Skip to content

Commit 1b555c1

Browse files
authored
Revert TraceContextPropagator performance refactor from 5749 (#6161)
1 parent 577337c commit 1b555c1

File tree

2 files changed

+28
-112
lines changed

2 files changed

+28
-112
lines changed

src/OpenTelemetry.Api/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md).
66

77
## Unreleased
88

9+
* Revert optimize performance of `TraceContextPropagator.Extract` introduced
10+
in #5749 to resolve #6158.
11+
([#6161](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6161))
12+
913
## 1.11.1
1014

1115
Released 2025-Jan-22

src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs

Lines changed: 24 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4-
using System.Buffers;
54
using System.Diagnostics;
65
using System.Runtime.CompilerServices;
6+
using System.Text;
77
using OpenTelemetry.Internal;
88

99
namespace OpenTelemetry.Context.Propagation;
@@ -76,7 +76,7 @@ public override PropagationContext Extract<T>(PropagationContext context, T carr
7676
var tracestateCollection = getter(carrier, TraceState);
7777
if (tracestateCollection?.Any() ?? false)
7878
{
79-
TryExtractTracestate(tracestateCollection, out tracestate);
79+
TryExtractTracestate(tracestateCollection.ToArray(), out tracestate);
8080
}
8181

8282
return new PropagationContext(
@@ -220,37 +220,31 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace
220220
return true;
221221
}
222222

223-
internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollection, out string tracestateResult)
223+
internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult)
224224
{
225225
tracestateResult = string.Empty;
226226

227-
char[]? rentedArray = null;
228-
Span<char> traceStateBuffer = stackalloc char[128]; // 256B
229-
Span<char> keyLookupBuffer = stackalloc char[96]; // 192B (3x32 keys)
230-
int keys = 0;
231-
int charsWritten = 0;
232-
233-
try
227+
if (tracestateCollection != null)
234228
{
235-
foreach (var tracestateItem in tracestateCollection)
229+
var keySet = new HashSet<string>();
230+
var result = new StringBuilder();
231+
for (int i = 0; i < tracestateCollection.Length; ++i)
236232
{
237-
var tracestate = tracestateItem.AsSpan();
238-
int position = 0;
239-
240-
while (position < tracestate.Length)
233+
var tracestate = tracestateCollection[i].AsSpan();
234+
int begin = 0;
235+
while (begin < tracestate.Length)
241236
{
242-
int length = tracestate.Slice(position).IndexOf(',');
237+
int length = tracestate.Slice(begin).IndexOf(',');
243238
ReadOnlySpan<char> listMember;
244-
245239
if (length != -1)
246240
{
247-
listMember = tracestate.Slice(position, length).Trim();
248-
position += length + 1;
241+
listMember = tracestate.Slice(begin, length).Trim();
242+
begin += length + 1;
249243
}
250244
else
251245
{
252-
listMember = tracestate.Slice(position).Trim();
253-
position = tracestate.Length;
246+
listMember = tracestate.Slice(begin).Trim();
247+
begin = tracestate.Length;
254248
}
255249

256250
// 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<string> tracestateCollecti
261255
continue;
262256
}
263257

264-
if (keys >= 32)
258+
if (keySet.Count >= 32)
265259
{
266260
// https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list
267261
// test_tracestate_member_count_limit
@@ -292,107 +286,25 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
292286
}
293287

294288
// ValidateKey() call above has ensured the key does not contain upper case letters.
295-
296-
var duplicationCheckLength = Math.Min(key.Length, 3);
297-
298-
if (keys > 0)
299-
{
300-
// Fast path check of first three chars for potential duplicated keys
301-
var potentialMatchingKeyPosition = 1;
302-
var found = false;
303-
for (int i = 0; i < keys * 3; i += 3)
304-
{
305-
if (keyLookupBuffer.Slice(i, duplicationCheckLength).SequenceEqual(key.Slice(0, duplicationCheckLength)))
306-
{
307-
found = true;
308-
break;
309-
}
310-
311-
potentialMatchingKeyPosition++;
312-
}
313-
314-
// If the fast check has found a possible duplicate, we need to do a full check
315-
if (found)
316-
{
317-
var bufferToCompare = traceStateBuffer.Slice(0, charsWritten);
318-
319-
// We know which key is the first possible duplicate, so skip to that key
320-
// by slicing to the position after the appropriate comma.
321-
for (int i = 1; i < potentialMatchingKeyPosition; i++)
322-
{
323-
var commaIndex = bufferToCompare.IndexOf(',');
324-
325-
if (commaIndex > -1)
326-
{
327-
bufferToCompare.Slice(commaIndex);
328-
}
329-
}
330-
331-
int existingIndex = -1;
332-
while ((existingIndex = bufferToCompare.IndexOf(key)) > -1)
333-
{
334-
if ((existingIndex > 0 && bufferToCompare[existingIndex - 1] != ',') || bufferToCompare[existingIndex + key.Length] != '=')
335-
{
336-
continue; // this is not a key
337-
}
338-
339-
return false; // test_tracestate_duplicated_keys
340-
}
341-
}
342-
}
343-
344-
// Store up to the first three characters of the key for use in the duplicate lookup fast path
345-
var startKeyLookupIndex = keys > 0 ? keys * 3 : 0;
346-
key.Slice(0, duplicationCheckLength).CopyTo(keyLookupBuffer.Slice(startKeyLookupIndex));
347-
348-
// Check we have capacity to write the key and value
349-
var requiredCapacity = charsWritten > 0 ? listMember.Length + 1 : listMember.Length;
350-
351-
while (charsWritten + requiredCapacity > traceStateBuffer.Length)
289+
if (!keySet.Add(key.ToString()))
352290
{
353-
GrowBuffer(ref rentedArray, ref traceStateBuffer);
291+
// test_tracestate_duplicated_keys
292+
return false;
354293
}
355294

356-
if (charsWritten > 0)
295+
if (result.Length > 0)
357296
{
358-
traceStateBuffer[charsWritten++] = ',';
297+
result.Append(',');
359298
}
360299

361-
listMember.CopyTo(traceStateBuffer.Slice(charsWritten));
362-
charsWritten += listMember.Length;
363-
364-
keys++;
300+
result.Append(listMember.ToString());
365301
}
366302
}
367303

368-
tracestateResult = traceStateBuffer.Slice(0, charsWritten).ToString();
369-
370-
return true;
304+
tracestateResult = result.ToString();
371305
}
372-
finally
373-
{
374-
if (rentedArray is not null)
375-
{
376-
ArrayPool<char>.Shared.Return(rentedArray);
377-
rentedArray = null;
378-
}
379-
}
380-
381-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
382-
static void GrowBuffer(ref char[]? array, ref Span<char> buffer)
383-
{
384-
var newBuffer = ArrayPool<char>.Shared.Rent(buffer.Length * 2);
385306

386-
buffer.CopyTo(newBuffer.AsSpan());
387-
388-
if (array is not null)
389-
{
390-
ArrayPool<char>.Shared.Return(array);
391-
}
392-
393-
array = newBuffer;
394-
buffer = array.AsSpan();
395-
}
307+
return true;
396308
}
397309

398310
private static byte HexCharToByte(char c)

0 commit comments

Comments
 (0)