diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index a726bf04cc5..a5a7c084b04 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md). ## Unreleased +* Revert optimize performance of `TraceContextPropagator.Extract` introduced + in #5749 to resolve #6158. + ([#6161](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6161)) + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs index ee67aaaaafb..8a8408a8b47 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs @@ -1,9 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text; using OpenTelemetry.Internal; namespace OpenTelemetry.Context.Propagation; @@ -76,7 +76,7 @@ public override PropagationContext Extract(PropagationContext context, T carr var tracestateCollection = getter(carrier, TraceState); if (tracestateCollection?.Any() ?? false) { - TryExtractTracestate(tracestateCollection, out tracestate); + TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); } return new PropagationContext( @@ -220,37 +220,31 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace return true; } - internal static bool TryExtractTracestate(IEnumerable tracestateCollection, out string tracestateResult) + internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult) { tracestateResult = string.Empty; - char[]? rentedArray = null; - Span traceStateBuffer = stackalloc char[128]; // 256B - Span keyLookupBuffer = stackalloc char[96]; // 192B (3x32 keys) - int keys = 0; - int charsWritten = 0; - - try + if (tracestateCollection != null) { - foreach (var tracestateItem in tracestateCollection) + var keySet = new HashSet(); + var result = new StringBuilder(); + for (int i = 0; i < tracestateCollection.Length; ++i) { - var tracestate = tracestateItem.AsSpan(); - int position = 0; - - while (position < tracestate.Length) + var tracestate = tracestateCollection[i].AsSpan(); + int begin = 0; + while (begin < tracestate.Length) { - int length = tracestate.Slice(position).IndexOf(','); + int length = tracestate.Slice(begin).IndexOf(','); ReadOnlySpan listMember; - if (length != -1) { - listMember = tracestate.Slice(position, length).Trim(); - position += length + 1; + listMember = tracestate.Slice(begin, length).Trim(); + begin += length + 1; } else { - listMember = tracestate.Slice(position).Trim(); - position = tracestate.Length; + listMember = tracestate.Slice(begin).Trim(); + begin = tracestate.Length; } // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values @@ -261,7 +255,7 @@ internal static bool TryExtractTracestate(IEnumerable tracestateCollecti continue; } - if (keys >= 32) + if (keySet.Count >= 32) { // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list // test_tracestate_member_count_limit @@ -292,107 +286,25 @@ internal static bool TryExtractTracestate(IEnumerable tracestateCollecti } // ValidateKey() call above has ensured the key does not contain upper case letters. - - var duplicationCheckLength = Math.Min(key.Length, 3); - - if (keys > 0) - { - // Fast path check of first three chars for potential duplicated keys - var potentialMatchingKeyPosition = 1; - var found = false; - for (int i = 0; i < keys * 3; i += 3) - { - if (keyLookupBuffer.Slice(i, duplicationCheckLength).SequenceEqual(key.Slice(0, duplicationCheckLength))) - { - found = true; - break; - } - - potentialMatchingKeyPosition++; - } - - // If the fast check has found a possible duplicate, we need to do a full check - if (found) - { - var bufferToCompare = traceStateBuffer.Slice(0, charsWritten); - - // We know which key is the first possible duplicate, so skip to that key - // by slicing to the position after the appropriate comma. - for (int i = 1; i < potentialMatchingKeyPosition; i++) - { - var commaIndex = bufferToCompare.IndexOf(','); - - if (commaIndex > -1) - { - bufferToCompare.Slice(commaIndex); - } - } - - int existingIndex = -1; - while ((existingIndex = bufferToCompare.IndexOf(key)) > -1) - { - if ((existingIndex > 0 && bufferToCompare[existingIndex - 1] != ',') || bufferToCompare[existingIndex + key.Length] != '=') - { - continue; // this is not a key - } - - return false; // test_tracestate_duplicated_keys - } - } - } - - // Store up to the first three characters of the key for use in the duplicate lookup fast path - var startKeyLookupIndex = keys > 0 ? keys * 3 : 0; - key.Slice(0, duplicationCheckLength).CopyTo(keyLookupBuffer.Slice(startKeyLookupIndex)); - - // Check we have capacity to write the key and value - var requiredCapacity = charsWritten > 0 ? listMember.Length + 1 : listMember.Length; - - while (charsWritten + requiredCapacity > traceStateBuffer.Length) + if (!keySet.Add(key.ToString())) { - GrowBuffer(ref rentedArray, ref traceStateBuffer); + // test_tracestate_duplicated_keys + return false; } - if (charsWritten > 0) + if (result.Length > 0) { - traceStateBuffer[charsWritten++] = ','; + result.Append(','); } - listMember.CopyTo(traceStateBuffer.Slice(charsWritten)); - charsWritten += listMember.Length; - - keys++; + result.Append(listMember.ToString()); } } - tracestateResult = traceStateBuffer.Slice(0, charsWritten).ToString(); - - return true; + tracestateResult = result.ToString(); } - finally - { - if (rentedArray is not null) - { - ArrayPool.Shared.Return(rentedArray); - rentedArray = null; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void GrowBuffer(ref char[]? array, ref Span buffer) - { - var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); - buffer.CopyTo(newBuffer.AsSpan()); - - if (array is not null) - { - ArrayPool.Shared.Return(array); - } - - array = newBuffer; - buffer = array.AsSpan(); - } + return true; } private static byte HexCharToByte(char c)