From 288e0a44c8f933abf42286b0b21c067de1abbb71 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 18 Feb 2026 10:24:19 -0800 Subject: [PATCH 1/5] Make trace serialization more paranoid --- .../Internal/MsgPack/MsgPackTraceExporter.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs index c7d310454b..a8af10e035 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #if NET +using System.Collections.Concurrent; using System.Collections.Frozen; #endif using System.Diagnostics; @@ -86,7 +87,7 @@ internal sealed class MsgPackTraceExporter : MsgPackExporter, IDisposable // this stores the prepopulated fields until CreateFraming can consume them into the prologue. // after CreateFraming is called, this dictionary is set to null, so don't use it after that. - private Dictionary prepopulatedFields; + private ConcurrentDictionary prepopulatedFields; private bool isDisposed; @@ -152,12 +153,12 @@ public MsgPackTraceExporter(GenevaExporterOptions options, Func resour this.userProvidedPrepopulatedFields = options.PrepopulatedFields != null && options.PrepopulatedFields.Count > 0; - this.prepopulatedFields = new Dictionary(0, StringComparer.Ordinal); + this.prepopulatedFields = new ConcurrentDictionary(StringComparer.Ordinal); if (options.PrepopulatedFields != null) { foreach (var entry in options.PrepopulatedFields) { - this.prepopulatedFields.Add(entry.Key, entry.Value); + this.prepopulatedFields[entry.Key] = entry.Value; } } @@ -347,15 +348,6 @@ internal void CreateFraming() var resourceAttributes = this.resourceProvider().Attributes; - if (this.resourceFieldNames != null) - { - // if ResourceFieldNames is set, we use resource attributes rather than PrepopulatedFields - this.prepopulatedFields = new Dictionary(0, StringComparer.Ordinal); - } - - // this is guaranteed to not be null because it's set in the constructor - Guard.ThrowIfNull(this.prepopulatedFields); - foreach (var resourceAttribute in resourceAttributes) { var key = resourceAttribute.Key; @@ -437,14 +429,17 @@ internal void CreateFraming() cursor = AddPartAField(buffer, cursor, entry.Key, entry.Value); } - this.bufferPrologue = new byte[cursor - 0]; - System.Buffer.BlockCopy(buffer, 0, this.bufferPrologue, 0, cursor - 0); + var bufferPrologue = new byte[cursor - 0]; + System.Buffer.BlockCopy(buffer, 0, bufferPrologue, 0, cursor - 0); // Now generate the epilogue cursor = MessagePackSerializer.Serialize(buffer, 0, new Dictionary { { "TimeFormat", "DateTime" } }); - this.bufferEpilogue = new byte[cursor - 0]; - System.Buffer.BlockCopy(buffer, 0, this.bufferEpilogue, 0, cursor - 0); + var bufferEpilogue = new byte[cursor - 0]; + System.Buffer.BlockCopy(buffer, 0, bufferEpilogue, 0, cursor - 0); + + this.bufferPrologue = bufferPrologue; + this.bufferEpilogue = bufferEpilogue; } internal ArraySegment SerializeActivity(Activity activity) From ce437bb7237f56286d9fbaabe5cfb80879a5d52c Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 18 Feb 2026 10:27:20 -0800 Subject: [PATCH 2/5] changelog part 1 --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 4aad8f8be7..e01ccdd154 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Improve multithreaded reliability of Geneva exporter. + ([#TODO](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/TODO)) + ## 1.15.0 Released 2026-Jan-21 From 9e414a5ef5905ed2ced019d88862d031a09c3fab Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 18 Feb 2026 10:29:27 -0800 Subject: [PATCH 3/5] changelog part 2 --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index e01ccdd154..eb8739aa7d 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Improve multithreaded reliability of Geneva exporter. - ([#TODO](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/TODO)) + ([#3881](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3881)) ## 1.15.0 From b699024b7e6e8a5f72f3c1e49db057216ddb5791 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 18 Feb 2026 10:37:25 -0800 Subject: [PATCH 4/5] fix netstandard2.0 --- .../Internal/MsgPack/MsgPackTraceExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs index a8af10e035..6fcb2d976b 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #if NET -using System.Collections.Concurrent; using System.Collections.Frozen; #endif +using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; From eb205a17016f370442100ff37029c5a97ca00814 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 18 Feb 2026 13:48:14 -0800 Subject: [PATCH 5/5] Update src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md Co-authored-by: Rajkumar Rangaraj --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index eb8739aa7d..fce0e4bacc 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -* Improve multithreaded reliability of Geneva exporter. +* Fixed race condition in `MsgPackTraceExporter` where concurrent threads + calling `SerializeActivity` would corrupt the `prepopulatedFields` dictionary, + leading to "Bad forward protocol format" errors. ([#3881](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3881)) ## 1.15.0