diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index bcbbc3f0bb..efa145b755 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Fix a bug in `GenevaMetricExporter` where the `MetricEtwDataTransport` singleton + is disposed. + ([#1537](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1537)) + ## 1.7.0 Released 2023-Dec-11 diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs index a7ab27898d..9383e80330 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs @@ -74,7 +74,7 @@ public GenevaMetricExporter(GenevaMetricExporterOptions options) case TransportProtocol.Unspecified: if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - this.metricDataTransport = MetricEtwDataTransport.Shared; + this.metricDataTransport = MetricEtwDataTransport.Instance; break; } else @@ -268,7 +268,12 @@ protected override void Dispose(bool disposing) { try { - this.metricDataTransport?.Dispose(); + // The ETW data transport singleton on Windows should NOT be disposed. + // On Linux, Unix Domain Socket is used and should be disposed. + if (this.metricDataTransport != MetricEtwDataTransport.Instance) + { + this.metricDataTransport.Dispose(); + } } catch (Exception ex) { diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricEtwDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricEtwDataTransport.cs index f83b10d9ee..61c06fa52d 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricEtwDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricEtwDataTransport.cs @@ -10,13 +10,9 @@ namespace OpenTelemetry.Exporter.Geneva; internal sealed class MetricEtwDataTransport : EventSource, IMetricDataTransport { private readonly int fixedPayloadEndIndex; + private bool isDisposed; - static MetricEtwDataTransport() - { - Shared = new(); - } - - public static readonly MetricEtwDataTransport Shared; + public static MetricEtwDataTransport Instance { get; private set; } = new(); private MetricEtwDataTransport() { @@ -57,4 +53,23 @@ private void ExternallyAggregatedDoubleDistributionMetric() private void TLVMetricEvent() { } + + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // No managed resources to release. + // The singleton instance is kept alive for the lifetime of the application. + // Set the instance to null so that future calls to the singleton property can fail explicitly. + Instance = null; + } + + this.isDisposed = true; + base.Dispose(disposing); + } } diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDataTransport.cs index 98d12a22f7..6ae7704ae1 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDataTransport.cs @@ -33,7 +33,7 @@ public void Dispose() return; } - this.udsDataTransport?.Dispose(); + this.udsDataTransport.Dispose(); this.isDisposed = true; } } diff --git a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs index 7e9c6f72a5..318cb02092 100644 --- a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs @@ -160,7 +160,7 @@ public void Dispose() try { // DO NOT Dispose eventBuilder, keyValuePairs, and partCFields as they are static - this.eventProvider?.Dispose(); + this.eventProvider.Dispose(); this.serializationData?.Dispose(); } catch (Exception ex) diff --git a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs index b5967affd7..95e4d15b82 100644 --- a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs @@ -153,7 +153,7 @@ public void Dispose() try { // DO NOT Dispose eventBuilder, keyValuePairs, and partCFields as they are static - this.eventProvider?.Dispose(); + this.eventProvider.Dispose(); } catch (Exception ex) { diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs index 2a254a1490..05043cd8f3 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs @@ -251,6 +251,35 @@ public void SuccessfulExportOnLinux() } } + [Fact] + public void MultipleCallsOnWindowsReusesSingletonEtwDataTransport() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var singleton = MetricEtwDataTransport.Instance; + this.EmitMetrics("one"); + Assert.Equal(singleton, MetricEtwDataTransport.Instance); + this.EmitMetrics("two"); + Assert.Equal(singleton, MetricEtwDataTransport.Instance); + } + } + + private void EmitMetrics(string attempt) + { + using var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() + .AddMeter("*") + .AddGenevaMetricExporter(x => + { + x.MetricExportIntervalMilliseconds = 1000; + x.ConnectionString = "Account=OTelGeneva;Namespace=MeteringSample"; + }) + .Build(); + + using var meter = new Meter("MeterName", "0.0.1"); + var counter = meter.CreateCounter("counter_" + attempt); + counter.Add(1); + } + [Theory] [InlineData(true)] [InlineData(false)]