From ae367417619c21ca2b4f965ed4a22d8f999c46a4 Mon Sep 17 00:00:00 2001 From: martincostello Date: Wed, 20 May 2026 11:49:17 +0100 Subject: [PATCH 1/2] [OTLP] Use ConditionalWeakTable Follow-up to #7303 to use `ConditionalWeakTable` instead of `ConcurrentDictionary`. --- .../Serializer/ProtobufOtlpResourceSerializer.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs index d6aa234837f..05480fbf233 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Buffers; -using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using OpenTelemetry.Resources; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; @@ -12,7 +12,7 @@ internal static class ProtobufOtlpResourceSerializer private const int ReserveSizeForLength = 4; private const int InitialBufferSize = 2048; - private static readonly ConcurrentDictionary CachedResourceBytes = new(); + private static readonly ConditionalWeakTable CachedResourceBytes = new(); private static ReadOnlySpan EmptyResourceBytes => [0x0A, 0x80, 0x80, 0x80, 0x00]; @@ -24,7 +24,12 @@ internal static int WriteResource(byte[] buffer, int writePosition, Resource? re return writePosition + EmptyResourceBytes.Length; } - var cached = CachedResourceBytes.GetOrAdd(resource, static r => SerializeResourceToBytes(r)); +#if NET10_0_OR_GREATER + var cached = CachedResourceBytes.GetOrAdd(resource, SerializeResourceToBytes); +#else + var cached = CachedResourceBytes.GetValue(resource, SerializeResourceToBytes); +#endif + Buffer.BlockCopy(cached, 0, buffer, writePosition, cached.Length); return writePosition + cached.Length; } @@ -32,8 +37,8 @@ internal static int WriteResource(byte[] buffer, int writePosition, Resource? re private static byte[] SerializeResourceToBytes(Resource resource) { var pool = ArrayPool.Shared; - var buffer = pool.Rent(InitialBufferSize); + try { while (true) From a69ea0bde9b515a65172efcf79d3689345f0970c Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 21 May 2026 09:08:26 +0100 Subject: [PATCH 2/2] [OTLP] Add unit test Add a unit test that verifies the resource isn't kept alive in the cache. --- .../OtlpResourceTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs index 5d139736b59..963f20b0689 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Runtime.CompilerServices; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; using OpenTelemetry.Proto.Trace.V1; using OpenTelemetry.Resources; @@ -65,4 +66,30 @@ public void ToOtlpResourceTest(bool includeServiceNameInResource) Assert.DoesNotContain(otlpResource.Attributes, kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName); } } + + [Fact] + public void WriteResourceDoesNotKeepResourceAlive() + { + var reference = CreateSerializedResourceWeakReference(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.False(reference.TryGetTarget(out _), "Resource should not be kept alive after serialization."); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference CreateSerializedResourceWeakReference() + { + var resource = ResourceBuilder.CreateEmpty() + .AddAttributes([new("key", "value")]) + .Build(); + + var buffer = new byte[1024]; + + _ = ProtobufOtlpResourceSerializer.WriteResource(buffer, 0, resource); + + return new WeakReference(resource); + } }