From f517ec535693926d50e9fb2b9b15214bcd3fff6a Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Tue, 22 Jun 2021 16:43:48 -0700 Subject: [PATCH 01/10] Initial prototype --- .../Hosting/src/Internal/HexConverter.cs | 354 ++++++++++++ .../Internal/HostingApplicationDiagnostics.cs | 51 +- .../Hosting/src/Internal/TextMapPropagator.cs | 520 ++++++++++++++++++ .../src/Microsoft.AspNetCore.Hosting.csproj | 1 + .../HostingApplicationDiagnosticsTests.cs | 4 +- 5 files changed, 908 insertions(+), 22 deletions(-) create mode 100644 src/Hosting/Hosting/src/Internal/HexConverter.cs create mode 100644 src/Hosting/Hosting/src/Internal/TextMapPropagator.cs diff --git a/src/Hosting/Hosting/src/Internal/HexConverter.cs b/src/Hosting/Hosting/src/Internal/HexConverter.cs new file mode 100644 index 000000000000..bd3f5737a495 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HexConverter.cs @@ -0,0 +1,354 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable +using System.Diagnostics; +using System.Runtime.CompilerServices; +#if SYSTEM_PRIVATE_CORELIB +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using Internal.Runtime.CompilerServices; +#endif + +namespace System +{ + internal static class HexConverter + { + public enum Casing : uint + { + // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. + Upper = 0, + + // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. + // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) + // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, + // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) + // don't have the 0x20 bit set, so ORing them maps to + // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. + Lower = 0x2020U, + } + + // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], + // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then + // subtract this integer from a constant minuend as shown below. + // + // [ 1000 1001 1000 1001 ] + // - [ 0000 HHHH 0000 LLLL ] + // ========================= + // [ *YYY **** *ZZZ **** ] + // + // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. + // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. + // (We don't care about the value of asterisked bits.) + // + // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). + // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). + // => hex := nibble + 55. + // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. + // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. + + // The commented out code below is code that directly implements the logic described above. + + // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); + // uint difference = 0x8989U - packedOriginalValues; + // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values + // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; + + // The code below is equivalent to the commented out code above but has been tweaked + // to allow codegen to make some extra optimizations. + + // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. + // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. + + // Finally, write to the output buffer starting with the *highest* index so that codegen can + // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) + + // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is + // writing to a span of known length (or the caller has already checked the bounds of the + // furthest access). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToBytesBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (byte)packedResult; + buffer[startingIndex] = (byte)(packedResult >> 8); + } + +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (char)(packedResult & 0xFF); + buffer[startingIndex] = (char)(packedResult >> 8); + } + +#if SYSTEM_PRIVATE_CORELIB + private static void EncodeToUtf16_Ssse3(ReadOnlySpan bytes, Span chars, Casing casing) + { + Debug.Assert(bytes.Length >= 4); + nint pos = 0; + + Vector128 shuffleMask = Vector128.Create( + 0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF, + 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF); + + Vector128 asciiTable = (casing == Casing.Upper) ? + Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'A', (byte)'B', + (byte)'C', (byte)'D', (byte)'E', (byte)'F') : + Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + do + { + // Read 32bits from "bytes" span at "pos" offset + uint block = Unsafe.ReadUnaligned( + ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos)); + + // Calculate nibbles + Vector128 lowNibbles = Ssse3.Shuffle( + Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask); + Vector128 highNibbles = Sse2.ShiftRightLogical( + Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte(); + + // Lookup the hex values at the positions of the indices + Vector128 indices = Sse2.And( + Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF)); + Vector128 hex = Ssse3.Shuffle(asciiTable, indices); + + // The high bytes (0x00) of the chars have also been converted + // to ascii hex '0', so clear them out. + hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte()); + + // Save to "chars" at pos*2 offset + Unsafe.WriteUnaligned( + ref Unsafe.As( + ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos * 2)), hex); + + pos += 4; + } while (pos < bytes.Length - 3); + + // Process trailing elements (bytes.Length % 4) + for (; pos < bytes.Length; pos++) + { + ToCharsBuffer(Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos), chars, (int)pos * 2, casing); + } + } +#endif + + public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Casing casing = Casing.Upper) + { + Debug.Assert(chars.Length >= bytes.Length * 2); + +#if SYSTEM_PRIVATE_CORELIB + if (Ssse3.IsSupported && bytes.Length >= 4) + { + EncodeToUtf16_Ssse3(bytes, chars, casing); + return; + } +#endif + for (int pos = 0; pos < bytes.Length; pos++) + { + ToCharsBuffer(bytes[pos], chars, pos * 2, casing); + } + } + +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = Casing.Upper) + { +#if NETFRAMEWORK || NETSTANDARD2_0 + Span result = stackalloc char[0]; + if (bytes.Length > 16) + { + var array = new char[bytes.Length * 2]; + result = array.AsSpan(); + } + else + { + result = stackalloc char[bytes.Length * 2]; + } + + int pos = 0; + foreach (byte b in bytes) + { + ToCharsBuffer(b, result, pos, casing); + pos += 2; + } + return result.ToString(); +#else + fixed (byte* bytesPtr = bytes) + { + return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), static (chars, args) => + { + var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length); + EncodeToUtf16(ros, chars, args.casing); + }); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharUpper(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('A' - ('9' + 1)); + } + + return (char)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharLower(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('a' - ('9' + 1)); + } + + return (char)value; + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) + { + return TryDecodeFromUtf16(chars, bytes, out _); + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes, out int charsProcessed) + { + Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); + Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); + + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. + if ((byteLo | byteHi) == 0xFF) + break; + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + i++; + + charsProcessed = i; + return (byteLo | byteHi) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromChar(int c) + { + return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromUpperChar(int c) + { + return c > 71 ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromLowerChar(int c) + { + if ((uint)(c - '0') <= '9' - '0') + return c - '0'; + + if ((uint)(c - 'a') <= 'f' - 'a') + return c - 'a' + 10; + + return 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexChar(int c) + { + if (IntPtr.Size == 8) + { + // This code path, when used, has no branches and doesn't depend on cache hits, + // so it's faster and does not vary in speed depending on input data distribution. + // We only use this logic on 64-bit systems, as using 64 bit values would otherwise + // be much slower than just using the lookup table anyway (no hardware support). + // The magic constant 18428868213665201664 is a 64 bit value containing 1s at the + // indices corresponding to all the valid hex characters (ie. "0123456789ABCDEFabcdef") + // minus 48 (ie. '0'), and backwards (so from the most significant bit and downwards). + // The offset of 48 for each bit is necessary so that the entire range fits in 64 bits. + // First, we subtract '0' to the input digit (after casting to uint to account for any + // negative inputs). Note that even if this subtraction underflows, this happens before + // the result is zero-extended to ulong, meaning that `i` will always have upper 32 bits + // equal to 0. We then left shift the constant with this offset, and apply a bitmask that + // has the highest bit set (the sign bit) if and only if `c` is in the ['0', '0' + 64) range. + // Then we only need to check whether this final result is less than 0: this will only be + // the case if both `i` was in fact the index of a set bit in the magic constant, and also + // `c` was in the allowed range (this ensures that false positive bit shifts are ignored). + ulong i = (uint)c - '0'; + ulong shift = 18428868213665201664UL << (int)i; + ulong mask = i - 64; + + return (long)(shift & mask) < 0 ? true : false; + } + + return FromChar(c) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexUpperChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexLowerChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); + } + + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + public static ReadOnlySpan CharToHexLookup => new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + }; + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 7d8a18db82db..7692daf5bce3 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Runtime.CompilerServices; using System.Web; using Microsoft.AspNetCore.Http; @@ -268,6 +269,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) [MethodImpl(MethodImplOptions.NoInlining)] private Activity? StartActivity(HttpContext httpContext, bool loggingEnabled, bool diagnosticListenerActivityCreationEnabled, out bool hasDiagnosticListener) { + var propagator = TextMapPropagator.DefaultPropagator; var activity = _activitySource.CreateActivity(ActivityName, ActivityKind.Server); if (activity is null && (loggingEnabled || diagnosticListenerActivityCreationEnabled)) { @@ -279,38 +281,47 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) { return null; } - var headers = httpContext.Request.Headers; - var requestId = headers.TraceParent; - if (requestId.Count == 0) - { - requestId = headers.RequestId; - } - if (!StringValues.IsNullOrEmpty(requestId)) + propagator.Extract(headers, (object carrier, string fieldName, out string? value) => { - activity.SetParentId(requestId); - var traceState = headers.TraceState; - if (traceState.Count > 0) + value = default; + var headers = ((IHeaderDictionary)carrier); + var values = headers[fieldName]; + if (values.Count == 0) { - activity.TraceStateString = traceState; + return false; } + value = values; + return true; + }, + + out string? requestId, out string? traceState); - // We expect baggage to be empty by default - // Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items) - var baggage = headers.GetCommaSeparatedValues(HeaderNames.Baggage); - if (baggage.Length == 0) + if (requestId is not null) + { + activity.SetParentId(requestId); + } + if (traceState is not null) + { + activity.TraceStateString = traceState; + } + + if (!string.IsNullOrEmpty(requestId)) + { + propagator.Extract(headers, (object carrier, string fieldName, out string? value) => { - baggage = headers.GetCommaSeparatedValues(HeaderNames.CorrelationContext); - } + value = headers[fieldName]; + return true; + }, out IEnumerable>? baggage); // AddBaggage adds items at the beginning of the list, so we need to add them in reverse to keep the same order as the client // An order could be important if baggage has two items with the same key (that is allowed by the contract) - for (var i = baggage.Length - 1; i >= 0; i--) + if (baggage is not null) { - if (NameValueHeaderValue.TryParse(baggage[i], out var baggageItem)) + foreach (var baggageItem in baggage.Reverse()) { - activity.AddBaggage(baggageItem.Name.ToString(), HttpUtility.UrlDecode(baggageItem.Value.ToString())); + activity.AddBaggage(baggageItem.Key, baggageItem.Value); } } } diff --git a/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs b/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs new file mode 100644 index 000000000000..0ce21045bedb --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs @@ -0,0 +1,520 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.Net; +using System.Collections.Generic; + +namespace System.Diagnostics +{ + internal abstract class TextMapPropagator + { + public delegate bool PropagatorGetterCallback(object carrier, string fieldName, out string? value); + + public abstract IReadOnlyCollection Fields { get; } + + // Inject + + public abstract bool Inject(Activity activity, object carrier, Action setter); + public abstract bool Inject(ActivityContext context, object carrier, Action setter); + public abstract bool Inject(IEnumerable> baggage, object carrier, Action setter); + + // Extract + + public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state); + public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context); + public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage); + + // + // Static APIs + // + + public static TextMapPropagator DefaultPropagator { get; set; } = CreateLegacyPropagator(); + + // For Microsoft compatibility. e.g., it will propagate Baggage header name as "Correlation-Context" instead of "baggage". + public static TextMapPropagator CreateLegacyPropagator() => new LegacyTextMapPropagator(); + + // Suppress context propagation. + public static TextMapPropagator CreateOutputSuppressionPropagator() => new OutputSuppressionPropagator(); + + // propagate only root parent context and ignore any intermediate created context. + public static TextMapPropagator CreatePassThroughPropagator() => new PassThroughPropagator(); + + // Conform to the W3C specs https://www.w3.org/TR/trace-context/ & https://www.w3.org/TR/2020/WD-baggage-20201020/ + public static TextMapPropagator CreateW3CPropagator() => new W3CPropagator(); + + // + // Internal + // + + internal const string TraceParent = "traceparent"; + internal const string RequestId = "Request-Id"; + internal const string TraceState = "tracestate"; + internal const string Baggage = "baggage"; + internal const string CorrelationContext = "Correlation-Context"; + internal static readonly char[] EqualSignSeparator = new[] { '=' }; + internal static readonly char[] CommaSignSeparator = new[] { ',' }; + internal const int MaxBaggageLength = 8192; + internal const int MaxBaggageItems = 180; + + internal static void InjectBaggage(object carrier, IEnumerable> baggage, Action setter, bool injectAsW3C = false) + { + using (IEnumerator> e = baggage.GetEnumerator()) + { + if (e.MoveNext()) + { + StringBuilder baggageList = new StringBuilder(); + do + { + KeyValuePair item = e.Current; + baggageList.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(','); + } + while (e.MoveNext()); + baggageList.Remove(baggageList.Length - 1, 1); + setter(carrier, injectAsW3C ? Baggage : CorrelationContext, baggageList.ToString()); + } + } + } + + internal static bool TryExtractBaggage(string baggagestring, out IEnumerable>? baggage) + { + baggage = null; + int baggageLength = -1; + List>? baggageList = null; + + if (string.IsNullOrEmpty(baggagestring)) + { + return true; + } + + foreach (string pair in baggagestring.Split(CommaSignSeparator)) + { + baggageLength += pair.Length + 1; // pair and comma + + if (baggageLength >= MaxBaggageLength || baggageList?.Count >= MaxBaggageItems) + { + break; + } + + if (pair.IndexOf('=') < 0) + { + continue; + } + + var parts = pair.Split(EqualSignSeparator, 2); + if (parts.Length != 2) + { + continue; + } + + var key = WebUtility.UrlDecode(parts[0]); + var value = WebUtility.UrlDecode(parts[1]); + + if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) + { + continue; + } + + if (baggageList is null) + { + baggageList = new(); + } + + baggageList.Add(new KeyValuePair(key, value)); + } + + baggage = baggageList; + return baggageList != null; + } + + internal static bool InjectContext(ActivityContext context, object carrier, Action setter) + { + if (context == default || setter is null || context.TraceId == default || context.SpanId == default) + { + return false; + } + + Span traceParent = stackalloc char[55]; + traceParent[0] = '0'; + traceParent[1] = '0'; + traceParent[2] = '-'; + traceParent[35] = '-'; + traceParent[52] = '-'; + CopyStringToSpan(context.TraceId.ToHexString(), traceParent.Slice(3, 32)); + CopyStringToSpan(context.SpanId.ToHexString(), traceParent.Slice(36, 16)); + HexConverter.ToCharsBuffer((byte)(context.TraceFlags & ActivityTraceFlags.Recorded), traceParent.Slice(53, 2), 0, HexConverter.Casing.Lower); + + setter(carrier, TraceParent, traceParent.ToString()); + + string? tracestateStr = context.TraceState; + if (tracestateStr?.Length > 0) + { + setter(carrier, TraceState, tracestateStr); + } + + return true; + } + + internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) + { + if (getter is null) + { + id = null; + state = null; + return false; + } + + getter(carrier, TraceParent, out id); + if (id is null) + { + getter(carrier, RequestId, out id); + } + + getter(carrier, TraceState, out state); + return true; + } + + internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) + { + context = default; + + if (getter is null) + { + return false; + } + + getter(carrier, TraceParent, out string? traceParent); + getter(carrier, TraceState, out string? traceState); + + return ActivityContext.TryParse(traceParent, traceState, out context); + } + + internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) + { + baggage = null; + if (getter is null) + { + return false; + } + + getter(carrier, Baggage, out string? theBaggage); + if (theBaggage is null || !TryExtractBaggage(theBaggage, out baggage)) + { + getter(carrier, CorrelationContext, out theBaggage); + if (theBaggage is not null) + { + TryExtractBaggage(theBaggage, out baggage); + } + } + + return true; + } + + internal static void CopyStringToSpan(string s, Span span) + { + Debug.Assert(s is not null); + Debug.Assert(s.Length == span.Length); + + for (int i = 0; i < s.Length; i++) + { + span[i] = s[i]; + } + } + + } + + internal class LegacyTextMapPropagator : TextMapPropagator + { + // + // Fields + // + + public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; + + // + // Inject + // + + public override bool Inject(Activity activity, object carrier, Action setter) + { + if (activity is null || setter == default) + { + return false; + } + + string? id = activity.Id; + if (id is null) + { + return false; + } + + if (activity.IdFormat == ActivityIdFormat.W3C) + { + setter(carrier, TraceParent, id); + if (activity.TraceStateString is not null) + { + setter(carrier, TraceState, activity.TraceStateString); + } + } + else + { + setter(carrier, RequestId, id); + } + + InjectBaggage(carrier, activity.Baggage, setter); + + return true; + } + + public override bool Inject(ActivityContext context, object carrier, Action setter) => + InjectContext(context, carrier, setter); + + public override bool Inject(IEnumerable> baggage, object carrier, Action setter) + { + if (setter is null) + { + return false; + } + + if (baggage is null) + { + return true; // nothing need to be done + } + + InjectBaggage(carrier, baggage, setter); + return true; + } + + // + // Extract + // + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => + LegacyExtract(carrier, getter, out id, out state); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => + LegacyExtract(carrier, getter, out context); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => + LegacyExtract(carrier, getter, out baggage); + } + + internal class PassThroughPropagator : TextMapPropagator + { + // + // Fields + // + public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; + + // Inject + + public override bool Inject(Activity activity, object carrier, Action setter) + { + GetRootId(out string? parentId, out string? traceState, out bool isW3c); + if (parentId is null) + { + return true; + } + + setter(carrier, isW3c ? TraceParent : RequestId, parentId); + + if (traceState is not null) + { + setter(carrier, TraceState, traceState); + } + + return true; + } + + public override bool Inject(ActivityContext context, object carrier, Action setter) => + Inject((Activity)null!, carrier, setter); + + public override bool Inject(IEnumerable> baggage, object carrier, Action setter) + { + IEnumerable>? parentBaggage = GetRootBaggage(); + + if (parentBaggage is not null) + { + InjectBaggage(carrier, parentBaggage, setter); + } + + return true; + } + + // + // Extract + // + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => + LegacyExtract(carrier, getter, out id, out state); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => + LegacyExtract(carrier, getter, out context); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => + LegacyExtract(carrier, getter, out baggage); + + private static void GetRootId(out string? parentId, out string? traceState, out bool isW3c) + { + Activity? activity = Activity.Current; + if (activity is null) + { + parentId = null; + traceState = null; + isW3c = false; + return; + } + + while (activity is not null && activity.Parent is not null) + { + activity = activity.Parent; + } + + traceState = activity?.TraceStateString; + parentId = activity?.ParentId ?? activity?.Id; + isW3c = activity?.IdFormat == ActivityIdFormat.W3C; + } + + private static IEnumerable>? GetRootBaggage() + { + Activity? activity = Activity.Current; + if (activity is null) + { + return null; + } + + while (activity is not null && activity.Parent is not null) + { + activity = activity.Parent; + } + + return activity?.Baggage; + } + } + + internal class OutputSuppressionPropagator : TextMapPropagator + { + // + // Fields + // + public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; + + // Inject + + public override bool Inject(Activity activity, object carrier, Action setter) => true; + public override bool Inject(ActivityContext context, object carrier, Action setter) => true; + public override bool Inject(IEnumerable> baggage, object carrier, Action setter) => true; + + // Extract + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => + LegacyExtract(carrier, getter, out id, out state); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => + LegacyExtract(carrier, getter, out context); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => + LegacyExtract(carrier, getter, out baggage); + } + + internal class W3CPropagator : TextMapPropagator + { + // + // Fields + // + + public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, TraceState, Baggage }; + + // + // Inject + // + + public override bool Inject(Activity activity, object carrier, Action setter) + { + if (activity is null || setter == default || activity.IdFormat != ActivityIdFormat.W3C) + { + return false; + } + + string? id = activity.Id; + if (id is null) + { + return false; + } + + setter(carrier, TraceParent, id); + if (activity.TraceStateString is not null) + { + setter(carrier, TraceState, activity.TraceStateString); + } + + InjectBaggage(carrier, activity.Baggage, setter, injectAsW3C: true); + + return true; + } + + public override bool Inject(ActivityContext context, object carrier, Action setter) => + InjectContext(context, carrier, setter); + + public override bool Inject(IEnumerable> baggage, object carrier, Action setter) + { + if (setter is null) + { + return false; + } + + if (baggage is null) + { + return true; // nothing need to be done + } + + InjectBaggage(carrier, baggage, setter, true); + return true; + } + + // + // Extract + // + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) + { + if (getter is null) + { + id = null; + state = null; + return false; + } + + getter(carrier, TraceParent, out id); + getter(carrier, TraceState, out state); + + if (id is not null && !ActivityContext.TryParse(id, state, out ActivityContext context)) + { + return false; + } + + return true; + } + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => + LegacyExtract(carrier, getter, out context); + + public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) + { + baggage = null; + if (getter is null) + { + return false; + } + + getter(carrier, Baggage, out string? theBaggage); + + if (theBaggage is not null) + { + return TryExtractBaggage(theBaggage, out baggage); + } + + return true; + } + } +} diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index d64782593742..a580adf0e68b 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -8,6 +8,7 @@ aspnetcore;hosting false enable + true diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 364d8a3e4af2..6dd4d9ca64e7 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -359,7 +359,7 @@ public void ActivityBaggagePrefersW3CBaggageHeaderName() hostingApplication.CreateContext(features); Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName); Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key1" && pair.Value == "value3"); - Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value4"); + Assert.Contains(Activity.Current.Baggage, pair => pair.Key == " Key2" && pair.Value == "value4"); } @@ -397,7 +397,7 @@ public void ActivityBaggagePreservesItemsOrder() KeyValuePair.Create("Key1","value3") }; - Assert.Equal(expectedBaggage, Activity.Current.Baggage); + Assert.Equal(expectedBaggage, Activity.Current.Baggage.ToArray()); } [Fact] From 4703a764e4c28d5cf3a5465b454edcc6e78f708f Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 23 Jun 2021 09:25:06 -0700 Subject: [PATCH 02/10] Fix tests --- .../Internal/HostingApplicationDiagnostics.cs | 4 +- .../Hosting/src/Internal/TextMapPropagator.cs | 104 +++++++++++++----- .../HostingApplicationDiagnosticsTests.cs | 2 +- 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 7692daf5bce3..a21d8b993f68 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -269,7 +269,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) [MethodImpl(MethodImplOptions.NoInlining)] private Activity? StartActivity(HttpContext httpContext, bool loggingEnabled, bool diagnosticListenerActivityCreationEnabled, out bool hasDiagnosticListener) { - var propagator = TextMapPropagator.DefaultPropagator; + var propagator = TextMapPropagator.Default; var activity = _activitySource.CreateActivity(ActivityName, ActivityKind.Server); if (activity is null && (loggingEnabled || diagnosticListenerActivityCreationEnabled)) { @@ -319,7 +319,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) // An order could be important if baggage has two items with the same key (that is allowed by the contract) if (baggage is not null) { - foreach (var baggageItem in baggage.Reverse()) + foreach (var baggageItem in baggage) { activity.AddBaggage(baggageItem.Key, baggageItem.Value); } diff --git a/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs b/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs index 0ce21045bedb..5e93bcd1c4f2 100644 --- a/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs +++ b/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs @@ -29,7 +29,7 @@ internal abstract class TextMapPropagator // Static APIs // - public static TextMapPropagator DefaultPropagator { get; set; } = CreateLegacyPropagator(); + public static TextMapPropagator Default { get; set; } = CreateLegacyPropagator(); // For Microsoft compatibility. e.g., it will propagate Baggage header name as "Correlation-Context" instead of "baggage". public static TextMapPropagator CreateLegacyPropagator() => new LegacyTextMapPropagator(); @@ -47,15 +47,22 @@ internal abstract class TextMapPropagator // Internal // + internal const char Space = ' '; + internal const char Tab = (char)9; + internal const char Comma = ','; + internal const char Semicolon = ';'; + + internal const int MaxBaggageLength = 8192; + internal const int MaxKeyValueLength = 4096; + internal const int MaxBaggageItems = 180; + internal const string TraceParent = "traceparent"; internal const string RequestId = "Request-Id"; internal const string TraceState = "tracestate"; internal const string Baggage = "baggage"; internal const string CorrelationContext = "Correlation-Context"; - internal static readonly char[] EqualSignSeparator = new[] { '=' }; - internal static readonly char[] CommaSignSeparator = new[] { ',' }; - internal const int MaxBaggageLength = 8192; - internal const int MaxBaggageItems = 180; + + internal static readonly char[] s_trimmingSpaceCharacters = new char[] { Space, Tab }; internal static void InjectBaggage(object carrier, IEnumerable> baggage, Action setter, bool injectAsW3C = false) { @@ -67,7 +74,7 @@ internal static void InjectBaggage(object carrier, IEnumerable item = e.Current; - baggageList.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(','); + baggageList.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(Comma); } while (e.MoveNext()); baggageList.Remove(baggageList.Length - 1, 1); @@ -87,41 +94,79 @@ internal static bool TryExtractBaggage(string baggagestring, out IEnumerable= MaxBaggageLength || baggageList?.Count >= MaxBaggageItems) - { - break; - } + if (currentIndex >= baggagestring.Length) { break; } // No Key exist - if (pair.IndexOf('=') < 0) - { - continue; - } + int keyStart = currentIndex; - var parts = pair.Split(EqualSignSeparator, 2); - if (parts.Length != 2) - { - continue; - } + // Search end of the key + while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Space && baggagestring[currentIndex] != Tab && baggagestring[currentIndex] != '=') { currentIndex++; } - var key = WebUtility.UrlDecode(parts[0]); - var value = WebUtility.UrlDecode(parts[1]); + if (currentIndex >= baggagestring.Length) { break; } - if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) + int keyEnd = currentIndex; + + if (baggagestring[currentIndex] != '=') { - continue; + // Skip Spaces + while (currentIndex < baggagestring.Length && (baggagestring[currentIndex] == Space || baggagestring[currentIndex] == Tab)) { currentIndex++; } + + if (currentIndex >= baggagestring.Length) { break; } // Wrong key format } - if (baggageList is null) + if (baggagestring[currentIndex] != '=') { break; } // wrong key format. + + currentIndex++; + + // Skip spaces + while (currentIndex < baggagestring.Length && (baggagestring[currentIndex] == Space || baggagestring[currentIndex] == Tab)) { currentIndex++; } + + if (currentIndex >= baggagestring.Length) { break; } // Wrong value format + + int valueStart = currentIndex; + + // Search end of the value + while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Space && baggagestring[currentIndex] != Tab && + baggagestring[currentIndex] != Comma && baggagestring[currentIndex] != Semicolon) + { currentIndex++; } + + if (keyStart < keyEnd && valueStart < currentIndex) { - baggageList = new(); + int keyValueLength = (keyEnd - keyStart) + (currentIndex - valueStart); + if (keyValueLength > MaxKeyValueLength || keyValueLength + baggageLength >= MaxBaggageLength) + { + break; + } + + if (baggageList is null) + { + baggageList = new(); + } + + baggageLength += keyValueLength; + + // Insert in reverse order for asp.net compatability. + baggageList.Insert(0, new KeyValuePair( + WebUtility.UrlDecode(baggagestring.Substring(keyStart, keyEnd - keyStart)).Trim(s_trimmingSpaceCharacters), + WebUtility.UrlDecode(baggagestring.Substring(valueStart, currentIndex - valueStart)).Trim(s_trimmingSpaceCharacters))); + + if (baggageList.Count >= MaxBaggageItems) + { + break; + } } - baggageList.Add(new KeyValuePair(key, value)); - } + // Skip to end of values + while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Comma) { currentIndex++; } + + currentIndex++; // Move to next key-value entry + } while (currentIndex < baggagestring.Length); baggage = baggageList; return baggageList != null; @@ -220,7 +265,6 @@ internal static void CopyStringToSpan(string s, Span span) span[i] = s[i]; } } - } internal class LegacyTextMapPropagator : TextMapPropagator diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 6dd4d9ca64e7..e41925b50734 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -359,7 +359,7 @@ public void ActivityBaggagePrefersW3CBaggageHeaderName() hostingApplication.CreateContext(features); Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName); Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key1" && pair.Value == "value3"); - Assert.Contains(Activity.Current.Baggage, pair => pair.Key == " Key2" && pair.Value == "value4"); + Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value4"); } From 4465c89ca7018abc7121645dd661a481e3fab867 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 23 Jun 2021 12:39:45 -0700 Subject: [PATCH 03/10] Remove closure allocation --- .../src/Internal/HostingApplicationDiagnostics.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index a21d8b993f68..0c8808d3195b 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -5,15 +5,10 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.Linq; using System.Runtime.CompilerServices; -using System.Web; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Hosting { @@ -283,7 +278,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) } var headers = httpContext.Request.Headers; - propagator.Extract(headers, (object carrier, string fieldName, out string? value) => + propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => { value = default; var headers = ((IHeaderDictionary)carrier); @@ -309,8 +304,9 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) if (!string.IsNullOrEmpty(requestId)) { - propagator.Extract(headers, (object carrier, string fieldName, out string? value) => + propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => { + var headers = ((IHeaderDictionary)carrier); value = headers[fieldName]; return true; }, out IEnumerable>? baggage); From 7a4d0315a28d2951f8fbe40dda46583b8a9c167d Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 28 Jun 2021 11:11:23 -0700 Subject: [PATCH 04/10] Resolve propagator from DI --- .../src/GenericHost/GenericWebHostBuilder.cs | 1 + .../Hosting/src/Internal/HostingApplication.cs | 3 ++- .../src/Internal/HostingApplicationDiagnostics.cs | 13 +++++++++---- src/Hosting/Hosting/src/Internal/WebHost.cs | 3 ++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index d71e8145dbdc..9d571d9b9d81 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -87,6 +87,7 @@ public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); + services.TryAddSingleton(sp => TextMapPropagator.Default); services.TryAddSingleton(); services.TryAddScoped(); diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs index 7e7e934511f2..2188655311e2 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplication.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplication.cs @@ -24,10 +24,11 @@ public HostingApplication( ILogger logger, DiagnosticListener diagnosticSource, ActivitySource activitySource, + TextMapPropagator propagator, IHttpContextFactory httpContextFactory) { _application = application; - _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource); + _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource, propagator); if (httpContextFactory is DefaultHttpContextFactory factory) { _defaultHttpContextFactory = factory; diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 0c8808d3195b..4f2b30f20e1b 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -27,13 +27,19 @@ internal class HostingApplicationDiagnostics private readonly ActivitySource _activitySource; private readonly DiagnosticListener _diagnosticListener; + private readonly TextMapPropagator _propagator; private readonly ILogger _logger; - public HostingApplicationDiagnostics(ILogger logger, DiagnosticListener diagnosticListener, ActivitySource activitySource) + public HostingApplicationDiagnostics( + ILogger logger, + DiagnosticListener diagnosticListener, + ActivitySource activitySource, + TextMapPropagator propagator) { _logger = logger; _diagnosticListener = diagnosticListener; _activitySource = activitySource; + _propagator = propagator; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -264,7 +270,6 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) [MethodImpl(MethodImplOptions.NoInlining)] private Activity? StartActivity(HttpContext httpContext, bool loggingEnabled, bool diagnosticListenerActivityCreationEnabled, out bool hasDiagnosticListener) { - var propagator = TextMapPropagator.Default; var activity = _activitySource.CreateActivity(ActivityName, ActivityKind.Server); if (activity is null && (loggingEnabled || diagnosticListenerActivityCreationEnabled)) { @@ -278,7 +283,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) } var headers = httpContext.Request.Headers; - propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => + _propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => { value = default; var headers = ((IHeaderDictionary)carrier); @@ -304,7 +309,7 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) if (!string.IsNullOrEmpty(requestId)) { - propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => + _propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => { var headers = ((IHeaderDictionary)carrier); value = headers[fieldName]; diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index 207f2b6ee20c..fa2995329318 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -157,8 +157,9 @@ public async Task StartAsync(CancellationToken cancellationToken = default) var diagnosticSource = _applicationServices.GetRequiredService(); var activitySource = _applicationServices.GetRequiredService(); + var propagator = _applicationServices.GetRequiredService(); var httpContextFactory = _applicationServices.GetRequiredService(); - var hostingApp = new HostingApplication(application, _logger, diagnosticSource, activitySource, httpContextFactory); + var hostingApp = new HostingApplication(application, _logger, diagnosticSource, activitySource, propagator, httpContextFactory); await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); _startedServer = true; From 511e8ef89918754c57e474cbe06024598584bce8 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 28 Jun 2021 11:19:51 -0700 Subject: [PATCH 05/10] Fix tests --- .../src/GenericHost/GenericWebHostedService.cs | 18 +++++++++++++++++- .../test/HostingApplicationDiagnosticsTests.cs | 1 + .../Hosting/test/HostingApplicationTests.cs | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs index 69d11d8f5187..43a46ed2bcfa 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs @@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Hosting { internal sealed partial class GenericWebHostService : IHostedService { +<<<<<<< HEAD public GenericWebHostService( IOptions options, IServer server, @@ -32,6 +33,19 @@ public GenericWebHostService( IEnumerable startupFilters, IConfiguration configuration, IWebHostEnvironment hostingEnvironment) +======= + public GenericWebHostService(IOptions options, + IServer server, + ILoggerFactory loggerFactory, + DiagnosticListener diagnosticListener, + ActivitySource activitySource, + TextMapPropagator propagator, + IHttpContextFactory httpContextFactory, + IApplicationBuilderFactory applicationBuilderFactory, + IEnumerable startupFilters, + IConfiguration configuration, + IWebHostEnvironment hostingEnvironment) +>>>>>>> 73ed5781a2 (Fix tests) { Options = options.Value; Server = server; @@ -39,6 +53,7 @@ public GenericWebHostService( LifetimeLogger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime"); DiagnosticListener = diagnosticListener; ActivitySource = activitySource; + Propagator = propagator; HttpContextFactory = httpContextFactory; ApplicationBuilderFactory = applicationBuilderFactory; StartupFilters = startupFilters; @@ -53,6 +68,7 @@ public GenericWebHostService( public ILogger LifetimeLogger { get; } public DiagnosticListener DiagnosticListener { get; } public ActivitySource ActivitySource { get; } + public TextMapPropagator Propagator { get; } public IHttpContextFactory HttpContextFactory { get; } public IApplicationBuilderFactory ApplicationBuilderFactory { get; } public IEnumerable StartupFilters { get; } @@ -116,7 +132,7 @@ public async Task StartAsync(CancellationToken cancellationToken) application = ErrorPageBuilder.BuildErrorPageApplication(HostingEnvironment.ContentRootFileProvider, Logger, showDetailedErrors, ex); } - var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, HttpContextFactory); + var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory); await Server.StartAsync(httpApplication, cancellationToken); diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index e41925b50734..90001b44bb12 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -552,6 +552,7 @@ private static HostingApplication CreateApplication(out FeatureCollection featur logger ?? new NullScopeLogger(), diagnosticListener ?? new NoopDiagnosticListener(), activitySource ?? new ActivitySource("Microsoft.AspNetCore"), + TextMapPropagator.Default, httpContextFactory.Object); return hostingApplication; diff --git a/src/Hosting/Hosting/test/HostingApplicationTests.cs b/src/Hosting/Hosting/test/HostingApplicationTests.cs index 217d81e79613..6c03929c51ed 100644 --- a/src/Hosting/Hosting/test/HostingApplicationTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationTests.cs @@ -196,6 +196,7 @@ private static HostingApplication CreateApplication(IHttpContextFactory httpCont NullLogger.Instance, new DiagnosticListener("Microsoft.AspNetCore"), activitySource ?? new ActivitySource("Microsoft.AspNetCore"), + TextMapPropagator.Default, httpContextFactory); return hostingApplication; From c0da476e6fc8c91d45f6e2496abfe1eb5049175e Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 28 Jun 2021 12:37:21 -0700 Subject: [PATCH 06/10] Update WebHostBuilder.cs --- .../src/GenericHost/GenericWebHostedService.cs | 14 -------------- .../src/Internal/HostingApplicationDiagnostics.cs | 1 + src/Hosting/Hosting/src/WebHostBuilder.cs | 1 + 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs index 43a46ed2bcfa..942de1b9be5e 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs @@ -21,19 +21,6 @@ namespace Microsoft.AspNetCore.Hosting { internal sealed partial class GenericWebHostService : IHostedService { -<<<<<<< HEAD - public GenericWebHostService( - IOptions options, - IServer server, - ILoggerFactory loggerFactory, - DiagnosticListener diagnosticListener, - ActivitySource activitySource, - IHttpContextFactory httpContextFactory, - IApplicationBuilderFactory applicationBuilderFactory, - IEnumerable startupFilters, - IConfiguration configuration, - IWebHostEnvironment hostingEnvironment) -======= public GenericWebHostService(IOptions options, IServer server, ILoggerFactory loggerFactory, @@ -45,7 +32,6 @@ public GenericWebHostService(IOptions options, IEnumerable startupFilters, IConfiguration configuration, IWebHostEnvironment hostingEnvironment) ->>>>>>> 73ed5781a2 (Fix tests) { Options = options.Value; Server = server; diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 4f2b30f20e1b..4f6258ed497a 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs index 53c3c8863c87..028c8ed1e49b 100644 --- a/src/Hosting/Hosting/src/WebHostBuilder.cs +++ b/src/Hosting/Hosting/src/WebHostBuilder.cs @@ -293,6 +293,7 @@ private IServiceCollection BuildCommonServices(out AggregateException? hostingSt services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); + services.TryAddSingleton(sp => TextMapPropagator.Default); services.AddTransient(); services.AddTransient(); From 196a45b87b28297b6a5a3679407680ca578347ed Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 14 Jul 2021 12:42:13 -0700 Subject: [PATCH 07/10] s/TextMapPropagator/DistributedContextPropagator --- .../src/GenericHost/GenericWebHostBuilder.cs | 2 +- .../GenericHost/GenericWebHostedService.cs | 4 +- .../src/Internal/HostingApplication.cs | 2 +- .../Internal/HostingApplicationDiagnostics.cs | 49 ++++++++++--------- src/Hosting/Hosting/src/Internal/WebHost.cs | 2 +- src/Hosting/Hosting/src/WebHostBuilder.cs | 2 +- .../HostingApplicationDiagnosticsTests.cs | 2 +- .../Hosting/test/HostingApplicationTests.cs | 2 +- 8 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index 9d571d9b9d81..db385520c1f4 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -87,7 +87,7 @@ public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); - services.TryAddSingleton(sp => TextMapPropagator.Default); + services.TryAddSingleton(DistributedContextPropagator.Current); services.TryAddSingleton(); services.TryAddScoped(); diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs index 942de1b9be5e..15fa43e9b4c6 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs @@ -26,7 +26,7 @@ public GenericWebHostService(IOptions options, ILoggerFactory loggerFactory, DiagnosticListener diagnosticListener, ActivitySource activitySource, - TextMapPropagator propagator, + DistributedContextPropagator propagator, IHttpContextFactory httpContextFactory, IApplicationBuilderFactory applicationBuilderFactory, IEnumerable startupFilters, @@ -54,7 +54,7 @@ public GenericWebHostService(IOptions options, public ILogger LifetimeLogger { get; } public DiagnosticListener DiagnosticListener { get; } public ActivitySource ActivitySource { get; } - public TextMapPropagator Propagator { get; } + public DistributedContextPropagator Propagator { get; } public IHttpContextFactory HttpContextFactory { get; } public IApplicationBuilderFactory ApplicationBuilderFactory { get; } public IEnumerable StartupFilters { get; } diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs index 2188655311e2..e51b9d2495df 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplication.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplication.cs @@ -24,7 +24,7 @@ public HostingApplication( ILogger logger, DiagnosticListener diagnosticSource, ActivitySource activitySource, - TextMapPropagator propagator, + DistributedContextPropagator propagator, IHttpContextFactory httpContextFactory) { _application = application; diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 4f6258ed497a..842dac2ddb61 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -28,14 +28,14 @@ internal class HostingApplicationDiagnostics private readonly ActivitySource _activitySource; private readonly DiagnosticListener _diagnosticListener; - private readonly TextMapPropagator _propagator; + private readonly DistributedContextPropagator _propagator; private readonly ILogger _logger; public HostingApplicationDiagnostics( ILogger logger, DiagnosticListener diagnosticListener, ActivitySource activitySource, - TextMapPropagator propagator) + DistributedContextPropagator propagator) { _logger = logger; _diagnosticListener = diagnosticListener; @@ -283,21 +283,20 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) return null; } var headers = httpContext.Request.Headers; - - _propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => - { - value = default; - var headers = ((IHeaderDictionary)carrier); - var values = headers[fieldName]; - if (values.Count == 0) + _propagator.ExtractTraceIdAndState(headers, + static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { - return false; - } - value = values; - return true; - }, - - out string? requestId, out string? traceState); + fieldValue = default; + fieldValues = default; + if (carrier is null) + { + return; + } + var headers = (IHeaderDictionary)carrier; + fieldValue = headers[fieldName]; + }, + out var requestId, + out var traceState); if (requestId is not null) { @@ -310,15 +309,19 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) if (!string.IsNullOrEmpty(requestId)) { - _propagator.Extract(headers, static (object carrier, string fieldName, out string? value) => + var baggage = _propagator.ExtractBaggage(headers, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { - var headers = ((IHeaderDictionary)carrier); - value = headers[fieldName]; - return true; - }, out IEnumerable>? baggage); + fieldValue = default; + fieldValues = default; + if (carrier is null) + { + return; + } + var headers = (IHeaderDictionary)carrier; + fieldValue = headers[fieldName]; + }); - // AddBaggage adds items at the beginning of the list, so we need to add them in reverse to keep the same order as the client - // An order could be important if baggage has two items with the same key (that is allowed by the contract) + // Order could be important if baggage has two items with the same key (that is allowed by the contract) if (baggage is not null) { foreach (var baggageItem in baggage) diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index fa2995329318..42552aa93373 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -157,7 +157,7 @@ public async Task StartAsync(CancellationToken cancellationToken = default) var diagnosticSource = _applicationServices.GetRequiredService(); var activitySource = _applicationServices.GetRequiredService(); - var propagator = _applicationServices.GetRequiredService(); + var propagator = _applicationServices.GetRequiredService(); var httpContextFactory = _applicationServices.GetRequiredService(); var hostingApp = new HostingApplication(application, _logger, diagnosticSource, activitySource, propagator, httpContextFactory); await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs index 028c8ed1e49b..5628110df244 100644 --- a/src/Hosting/Hosting/src/WebHostBuilder.cs +++ b/src/Hosting/Hosting/src/WebHostBuilder.cs @@ -293,7 +293,7 @@ private IServiceCollection BuildCommonServices(out AggregateException? hostingSt services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); - services.TryAddSingleton(sp => TextMapPropagator.Default); + services.TryAddSingleton(DistributedContextPropagator.Current); services.AddTransient(); services.AddTransient(); diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 90001b44bb12..85ea78c2647b 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -552,7 +552,7 @@ private static HostingApplication CreateApplication(out FeatureCollection featur logger ?? new NullScopeLogger(), diagnosticListener ?? new NoopDiagnosticListener(), activitySource ?? new ActivitySource("Microsoft.AspNetCore"), - TextMapPropagator.Default, + DistributedContextPropagator.CreateDefaultPropagator(), httpContextFactory.Object); return hostingApplication; diff --git a/src/Hosting/Hosting/test/HostingApplicationTests.cs b/src/Hosting/Hosting/test/HostingApplicationTests.cs index 6c03929c51ed..7bafdf05b16a 100644 --- a/src/Hosting/Hosting/test/HostingApplicationTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationTests.cs @@ -196,7 +196,7 @@ private static HostingApplication CreateApplication(IHttpContextFactory httpCont NullLogger.Instance, new DiagnosticListener("Microsoft.AspNetCore"), activitySource ?? new ActivitySource("Microsoft.AspNetCore"), - TextMapPropagator.Default, + DistributedContextPropagator.CreateDefaultPropagator(), httpContextFactory); return hostingApplication; From abcc758e8e2255a96beaa1ffca03525a3f5c996e Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 14 Jul 2021 12:42:24 -0700 Subject: [PATCH 08/10] Remove unused files --- .../Hosting/src/Internal/HexConverter.cs | 354 ----------- .../Hosting/src/Internal/TextMapPropagator.cs | 564 ------------------ 2 files changed, 918 deletions(-) delete mode 100644 src/Hosting/Hosting/src/Internal/HexConverter.cs delete mode 100644 src/Hosting/Hosting/src/Internal/TextMapPropagator.cs diff --git a/src/Hosting/Hosting/src/Internal/HexConverter.cs b/src/Hosting/Hosting/src/Internal/HexConverter.cs deleted file mode 100644 index bd3f5737a495..000000000000 --- a/src/Hosting/Hosting/src/Internal/HexConverter.cs +++ /dev/null @@ -1,354 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable -using System.Diagnostics; -using System.Runtime.CompilerServices; -#if SYSTEM_PRIVATE_CORELIB -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using Internal.Runtime.CompilerServices; -#endif - -namespace System -{ - internal static class HexConverter - { - public enum Casing : uint - { - // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. - Upper = 0, - - // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. - // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) - // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, - // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) - // don't have the 0x20 bit set, so ORing them maps to - // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. - Lower = 0x2020U, - } - - // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], - // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then - // subtract this integer from a constant minuend as shown below. - // - // [ 1000 1001 1000 1001 ] - // - [ 0000 HHHH 0000 LLLL ] - // ========================= - // [ *YYY **** *ZZZ **** ] - // - // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. - // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. - // (We don't care about the value of asterisked bits.) - // - // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). - // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). - // => hex := nibble + 55. - // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. - // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. - - // The commented out code below is code that directly implements the logic described above. - - // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); - // uint difference = 0x8989U - packedOriginalValues; - // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values - // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; - - // The code below is equivalent to the commented out code above but has been tweaked - // to allow codegen to make some extra optimizations. - - // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. - // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. - - // Finally, write to the output buffer starting with the *highest* index so that codegen can - // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) - - // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is - // writing to a span of known length (or the caller has already checked the bounds of the - // furthest access). - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToBytesBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) - { - uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; - uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; - - buffer[startingIndex + 1] = (byte)packedResult; - buffer[startingIndex] = (byte)(packedResult >> 8); - } - -#if ALLOW_PARTIALLY_TRUSTED_CALLERS - [System.Security.SecuritySafeCriticalAttribute] -#endif - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) - { - uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; - uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; - - buffer[startingIndex + 1] = (char)(packedResult & 0xFF); - buffer[startingIndex] = (char)(packedResult >> 8); - } - -#if SYSTEM_PRIVATE_CORELIB - private static void EncodeToUtf16_Ssse3(ReadOnlySpan bytes, Span chars, Casing casing) - { - Debug.Assert(bytes.Length >= 4); - nint pos = 0; - - Vector128 shuffleMask = Vector128.Create( - 0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF, - 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF); - - Vector128 asciiTable = (casing == Casing.Upper) ? - Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'A', (byte)'B', - (byte)'C', (byte)'D', (byte)'E', (byte)'F') : - Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', - (byte)'c', (byte)'d', (byte)'e', (byte)'f'); - - do - { - // Read 32bits from "bytes" span at "pos" offset - uint block = Unsafe.ReadUnaligned( - ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos)); - - // Calculate nibbles - Vector128 lowNibbles = Ssse3.Shuffle( - Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask); - Vector128 highNibbles = Sse2.ShiftRightLogical( - Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte(); - - // Lookup the hex values at the positions of the indices - Vector128 indices = Sse2.And( - Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF)); - Vector128 hex = Ssse3.Shuffle(asciiTable, indices); - - // The high bytes (0x00) of the chars have also been converted - // to ascii hex '0', so clear them out. - hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte()); - - // Save to "chars" at pos*2 offset - Unsafe.WriteUnaligned( - ref Unsafe.As( - ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos * 2)), hex); - - pos += 4; - } while (pos < bytes.Length - 3); - - // Process trailing elements (bytes.Length % 4) - for (; pos < bytes.Length; pos++) - { - ToCharsBuffer(Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos), chars, (int)pos * 2, casing); - } - } -#endif - - public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Casing casing = Casing.Upper) - { - Debug.Assert(chars.Length >= bytes.Length * 2); - -#if SYSTEM_PRIVATE_CORELIB - if (Ssse3.IsSupported && bytes.Length >= 4) - { - EncodeToUtf16_Ssse3(bytes, chars, casing); - return; - } -#endif - for (int pos = 0; pos < bytes.Length; pos++) - { - ToCharsBuffer(bytes[pos], chars, pos * 2, casing); - } - } - -#if ALLOW_PARTIALLY_TRUSTED_CALLERS - [System.Security.SecuritySafeCriticalAttribute] -#endif - public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = Casing.Upper) - { -#if NETFRAMEWORK || NETSTANDARD2_0 - Span result = stackalloc char[0]; - if (bytes.Length > 16) - { - var array = new char[bytes.Length * 2]; - result = array.AsSpan(); - } - else - { - result = stackalloc char[bytes.Length * 2]; - } - - int pos = 0; - foreach (byte b in bytes) - { - ToCharsBuffer(b, result, pos, casing); - pos += 2; - } - return result.ToString(); -#else - fixed (byte* bytesPtr = bytes) - { - return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), static (chars, args) => - { - var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length); - EncodeToUtf16(ros, chars, args.casing); - }); - } -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static char ToCharUpper(int value) - { - value &= 0xF; - value += '0'; - - if (value > '9') - { - value += ('A' - ('9' + 1)); - } - - return (char)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static char ToCharLower(int value) - { - value &= 0xF; - value += '0'; - - if (value > '9') - { - value += ('a' - ('9' + 1)); - } - - return (char)value; - } - - public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) - { - return TryDecodeFromUtf16(chars, bytes, out _); - } - - public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes, out int charsProcessed) - { - Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); - Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); - - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; - while (j < bytes.Length) - { - byteLo = FromChar(chars[i + 1]); - byteHi = FromChar(chars[i]); - - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - break; - - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; - } - - if (byteLo == 0xFF) - i++; - - charsProcessed = i; - return (byteLo | byteHi) != 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromChar(int c) - { - return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromUpperChar(int c) - { - return c > 71 ? 0xFF : CharToHexLookup[c]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromLowerChar(int c) - { - if ((uint)(c - '0') <= '9' - '0') - return c - '0'; - - if ((uint)(c - 'a') <= 'f' - 'a') - return c - 'a' + 10; - - return 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexChar(int c) - { - if (IntPtr.Size == 8) - { - // This code path, when used, has no branches and doesn't depend on cache hits, - // so it's faster and does not vary in speed depending on input data distribution. - // We only use this logic on 64-bit systems, as using 64 bit values would otherwise - // be much slower than just using the lookup table anyway (no hardware support). - // The magic constant 18428868213665201664 is a 64 bit value containing 1s at the - // indices corresponding to all the valid hex characters (ie. "0123456789ABCDEFabcdef") - // minus 48 (ie. '0'), and backwards (so from the most significant bit and downwards). - // The offset of 48 for each bit is necessary so that the entire range fits in 64 bits. - // First, we subtract '0' to the input digit (after casting to uint to account for any - // negative inputs). Note that even if this subtraction underflows, this happens before - // the result is zero-extended to ulong, meaning that `i` will always have upper 32 bits - // equal to 0. We then left shift the constant with this offset, and apply a bitmask that - // has the highest bit set (the sign bit) if and only if `c` is in the ['0', '0' + 64) range. - // Then we only need to check whether this final result is less than 0: this will only be - // the case if both `i` was in fact the index of a set bit in the magic constant, and also - // `c` was in the allowed range (this ensures that false positive bit shifts are ignored). - ulong i = (uint)c - '0'; - ulong shift = 18428868213665201664UL << (int)i; - ulong mask = i - 64; - - return (long)(shift & mask) < 0 ? true : false; - } - - return FromChar(c) != 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexUpperChar(int c) - { - return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexLowerChar(int c) - { - return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); - } - - /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - public static ReadOnlySpan CharToHexLookup => new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 - }; - } -} diff --git a/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs b/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs deleted file mode 100644 index 5e93bcd1c4f2..000000000000 --- a/src/Hosting/Hosting/src/Internal/TextMapPropagator.cs +++ /dev/null @@ -1,564 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text; -using System.Net; -using System.Collections.Generic; - -namespace System.Diagnostics -{ - internal abstract class TextMapPropagator - { - public delegate bool PropagatorGetterCallback(object carrier, string fieldName, out string? value); - - public abstract IReadOnlyCollection Fields { get; } - - // Inject - - public abstract bool Inject(Activity activity, object carrier, Action setter); - public abstract bool Inject(ActivityContext context, object carrier, Action setter); - public abstract bool Inject(IEnumerable> baggage, object carrier, Action setter); - - // Extract - - public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state); - public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context); - public abstract bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage); - - // - // Static APIs - // - - public static TextMapPropagator Default { get; set; } = CreateLegacyPropagator(); - - // For Microsoft compatibility. e.g., it will propagate Baggage header name as "Correlation-Context" instead of "baggage". - public static TextMapPropagator CreateLegacyPropagator() => new LegacyTextMapPropagator(); - - // Suppress context propagation. - public static TextMapPropagator CreateOutputSuppressionPropagator() => new OutputSuppressionPropagator(); - - // propagate only root parent context and ignore any intermediate created context. - public static TextMapPropagator CreatePassThroughPropagator() => new PassThroughPropagator(); - - // Conform to the W3C specs https://www.w3.org/TR/trace-context/ & https://www.w3.org/TR/2020/WD-baggage-20201020/ - public static TextMapPropagator CreateW3CPropagator() => new W3CPropagator(); - - // - // Internal - // - - internal const char Space = ' '; - internal const char Tab = (char)9; - internal const char Comma = ','; - internal const char Semicolon = ';'; - - internal const int MaxBaggageLength = 8192; - internal const int MaxKeyValueLength = 4096; - internal const int MaxBaggageItems = 180; - - internal const string TraceParent = "traceparent"; - internal const string RequestId = "Request-Id"; - internal const string TraceState = "tracestate"; - internal const string Baggage = "baggage"; - internal const string CorrelationContext = "Correlation-Context"; - - internal static readonly char[] s_trimmingSpaceCharacters = new char[] { Space, Tab }; - - internal static void InjectBaggage(object carrier, IEnumerable> baggage, Action setter, bool injectAsW3C = false) - { - using (IEnumerator> e = baggage.GetEnumerator()) - { - if (e.MoveNext()) - { - StringBuilder baggageList = new StringBuilder(); - do - { - KeyValuePair item = e.Current; - baggageList.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(Comma); - } - while (e.MoveNext()); - baggageList.Remove(baggageList.Length - 1, 1); - setter(carrier, injectAsW3C ? Baggage : CorrelationContext, baggageList.ToString()); - } - } - } - - internal static bool TryExtractBaggage(string baggagestring, out IEnumerable>? baggage) - { - baggage = null; - int baggageLength = -1; - List>? baggageList = null; - - if (string.IsNullOrEmpty(baggagestring)) - { - return true; - } - - int currentIndex = 0; - - do - { - // Skip spaces - while (currentIndex < baggagestring.Length && (baggagestring[currentIndex] == Space || baggagestring[currentIndex] == Tab)) { currentIndex++; } - - if (currentIndex >= baggagestring.Length) { break; } // No Key exist - - int keyStart = currentIndex; - - // Search end of the key - while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Space && baggagestring[currentIndex] != Tab && baggagestring[currentIndex] != '=') { currentIndex++; } - - if (currentIndex >= baggagestring.Length) { break; } - - int keyEnd = currentIndex; - - if (baggagestring[currentIndex] != '=') - { - // Skip Spaces - while (currentIndex < baggagestring.Length && (baggagestring[currentIndex] == Space || baggagestring[currentIndex] == Tab)) { currentIndex++; } - - if (currentIndex >= baggagestring.Length) { break; } // Wrong key format - } - - if (baggagestring[currentIndex] != '=') { break; } // wrong key format. - - currentIndex++; - - // Skip spaces - while (currentIndex < baggagestring.Length && (baggagestring[currentIndex] == Space || baggagestring[currentIndex] == Tab)) { currentIndex++; } - - if (currentIndex >= baggagestring.Length) { break; } // Wrong value format - - int valueStart = currentIndex; - - // Search end of the value - while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Space && baggagestring[currentIndex] != Tab && - baggagestring[currentIndex] != Comma && baggagestring[currentIndex] != Semicolon) - { currentIndex++; } - - if (keyStart < keyEnd && valueStart < currentIndex) - { - int keyValueLength = (keyEnd - keyStart) + (currentIndex - valueStart); - if (keyValueLength > MaxKeyValueLength || keyValueLength + baggageLength >= MaxBaggageLength) - { - break; - } - - if (baggageList is null) - { - baggageList = new(); - } - - baggageLength += keyValueLength; - - // Insert in reverse order for asp.net compatability. - baggageList.Insert(0, new KeyValuePair( - WebUtility.UrlDecode(baggagestring.Substring(keyStart, keyEnd - keyStart)).Trim(s_trimmingSpaceCharacters), - WebUtility.UrlDecode(baggagestring.Substring(valueStart, currentIndex - valueStart)).Trim(s_trimmingSpaceCharacters))); - - if (baggageList.Count >= MaxBaggageItems) - { - break; - } - } - - // Skip to end of values - while (currentIndex < baggagestring.Length && baggagestring[currentIndex] != Comma) { currentIndex++; } - - currentIndex++; // Move to next key-value entry - } while (currentIndex < baggagestring.Length); - - baggage = baggageList; - return baggageList != null; - } - - internal static bool InjectContext(ActivityContext context, object carrier, Action setter) - { - if (context == default || setter is null || context.TraceId == default || context.SpanId == default) - { - return false; - } - - Span traceParent = stackalloc char[55]; - traceParent[0] = '0'; - traceParent[1] = '0'; - traceParent[2] = '-'; - traceParent[35] = '-'; - traceParent[52] = '-'; - CopyStringToSpan(context.TraceId.ToHexString(), traceParent.Slice(3, 32)); - CopyStringToSpan(context.SpanId.ToHexString(), traceParent.Slice(36, 16)); - HexConverter.ToCharsBuffer((byte)(context.TraceFlags & ActivityTraceFlags.Recorded), traceParent.Slice(53, 2), 0, HexConverter.Casing.Lower); - - setter(carrier, TraceParent, traceParent.ToString()); - - string? tracestateStr = context.TraceState; - if (tracestateStr?.Length > 0) - { - setter(carrier, TraceState, tracestateStr); - } - - return true; - } - - internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) - { - if (getter is null) - { - id = null; - state = null; - return false; - } - - getter(carrier, TraceParent, out id); - if (id is null) - { - getter(carrier, RequestId, out id); - } - - getter(carrier, TraceState, out state); - return true; - } - - internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) - { - context = default; - - if (getter is null) - { - return false; - } - - getter(carrier, TraceParent, out string? traceParent); - getter(carrier, TraceState, out string? traceState); - - return ActivityContext.TryParse(traceParent, traceState, out context); - } - - internal static bool LegacyExtract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) - { - baggage = null; - if (getter is null) - { - return false; - } - - getter(carrier, Baggage, out string? theBaggage); - if (theBaggage is null || !TryExtractBaggage(theBaggage, out baggage)) - { - getter(carrier, CorrelationContext, out theBaggage); - if (theBaggage is not null) - { - TryExtractBaggage(theBaggage, out baggage); - } - } - - return true; - } - - internal static void CopyStringToSpan(string s, Span span) - { - Debug.Assert(s is not null); - Debug.Assert(s.Length == span.Length); - - for (int i = 0; i < s.Length; i++) - { - span[i] = s[i]; - } - } - } - - internal class LegacyTextMapPropagator : TextMapPropagator - { - // - // Fields - // - - public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; - - // - // Inject - // - - public override bool Inject(Activity activity, object carrier, Action setter) - { - if (activity is null || setter == default) - { - return false; - } - - string? id = activity.Id; - if (id is null) - { - return false; - } - - if (activity.IdFormat == ActivityIdFormat.W3C) - { - setter(carrier, TraceParent, id); - if (activity.TraceStateString is not null) - { - setter(carrier, TraceState, activity.TraceStateString); - } - } - else - { - setter(carrier, RequestId, id); - } - - InjectBaggage(carrier, activity.Baggage, setter); - - return true; - } - - public override bool Inject(ActivityContext context, object carrier, Action setter) => - InjectContext(context, carrier, setter); - - public override bool Inject(IEnumerable> baggage, object carrier, Action setter) - { - if (setter is null) - { - return false; - } - - if (baggage is null) - { - return true; // nothing need to be done - } - - InjectBaggage(carrier, baggage, setter); - return true; - } - - // - // Extract - // - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => - LegacyExtract(carrier, getter, out id, out state); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => - LegacyExtract(carrier, getter, out context); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => - LegacyExtract(carrier, getter, out baggage); - } - - internal class PassThroughPropagator : TextMapPropagator - { - // - // Fields - // - public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; - - // Inject - - public override bool Inject(Activity activity, object carrier, Action setter) - { - GetRootId(out string? parentId, out string? traceState, out bool isW3c); - if (parentId is null) - { - return true; - } - - setter(carrier, isW3c ? TraceParent : RequestId, parentId); - - if (traceState is not null) - { - setter(carrier, TraceState, traceState); - } - - return true; - } - - public override bool Inject(ActivityContext context, object carrier, Action setter) => - Inject((Activity)null!, carrier, setter); - - public override bool Inject(IEnumerable> baggage, object carrier, Action setter) - { - IEnumerable>? parentBaggage = GetRootBaggage(); - - if (parentBaggage is not null) - { - InjectBaggage(carrier, parentBaggage, setter); - } - - return true; - } - - // - // Extract - // - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => - LegacyExtract(carrier, getter, out id, out state); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => - LegacyExtract(carrier, getter, out context); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => - LegacyExtract(carrier, getter, out baggage); - - private static void GetRootId(out string? parentId, out string? traceState, out bool isW3c) - { - Activity? activity = Activity.Current; - if (activity is null) - { - parentId = null; - traceState = null; - isW3c = false; - return; - } - - while (activity is not null && activity.Parent is not null) - { - activity = activity.Parent; - } - - traceState = activity?.TraceStateString; - parentId = activity?.ParentId ?? activity?.Id; - isW3c = activity?.IdFormat == ActivityIdFormat.W3C; - } - - private static IEnumerable>? GetRootBaggage() - { - Activity? activity = Activity.Current; - if (activity is null) - { - return null; - } - - while (activity is not null && activity.Parent is not null) - { - activity = activity.Parent; - } - - return activity?.Baggage; - } - } - - internal class OutputSuppressionPropagator : TextMapPropagator - { - // - // Fields - // - public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }; - - // Inject - - public override bool Inject(Activity activity, object carrier, Action setter) => true; - public override bool Inject(ActivityContext context, object carrier, Action setter) => true; - public override bool Inject(IEnumerable> baggage, object carrier, Action setter) => true; - - // Extract - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) => - LegacyExtract(carrier, getter, out id, out state); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => - LegacyExtract(carrier, getter, out context); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) => - LegacyExtract(carrier, getter, out baggage); - } - - internal class W3CPropagator : TextMapPropagator - { - // - // Fields - // - - public override IReadOnlyCollection Fields { get; } = new HashSet() { TraceParent, TraceState, Baggage }; - - // - // Inject - // - - public override bool Inject(Activity activity, object carrier, Action setter) - { - if (activity is null || setter == default || activity.IdFormat != ActivityIdFormat.W3C) - { - return false; - } - - string? id = activity.Id; - if (id is null) - { - return false; - } - - setter(carrier, TraceParent, id); - if (activity.TraceStateString is not null) - { - setter(carrier, TraceState, activity.TraceStateString); - } - - InjectBaggage(carrier, activity.Baggage, setter, injectAsW3C: true); - - return true; - } - - public override bool Inject(ActivityContext context, object carrier, Action setter) => - InjectContext(context, carrier, setter); - - public override bool Inject(IEnumerable> baggage, object carrier, Action setter) - { - if (setter is null) - { - return false; - } - - if (baggage is null) - { - return true; // nothing need to be done - } - - InjectBaggage(carrier, baggage, setter, true); - return true; - } - - // - // Extract - // - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state) - { - if (getter is null) - { - id = null; - state = null; - return false; - } - - getter(carrier, TraceParent, out id); - getter(carrier, TraceState, out state); - - if (id is not null && !ActivityContext.TryParse(id, state, out ActivityContext context)) - { - return false; - } - - return true; - } - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out ActivityContext context) => - LegacyExtract(carrier, getter, out context); - - public override bool Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable>? baggage) - { - baggage = null; - if (getter is null) - { - return false; - } - - getter(carrier, Baggage, out string? theBaggage); - - if (theBaggage is not null) - { - return TryExtractBaggage(theBaggage, out baggage); - } - - return true; - } - } -} From df05c15c9104db694294ca1861ea9e80e587c25c Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 14 Jul 2021 12:46:08 -0700 Subject: [PATCH 09/10] Remove unsafeblocks in hosting --- src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index a580adf0e68b..d64782593742 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -8,7 +8,6 @@ aspnetcore;hosting false enable - true From ce58b192bcdd3d6be0139ca25999eae63c6070d2 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 14 Jul 2021 14:02:11 -0700 Subject: [PATCH 10/10] HttpContext.Request.Headers can't be null --- .../Internal/HostingApplicationDiagnostics.cs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 842dac2ddb61..a5148fda6902 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -286,38 +286,24 @@ private static void RecordRequestStartEventLog(HttpContext httpContext) _propagator.ExtractTraceIdAndState(headers, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { - fieldValue = default; fieldValues = default; - if (carrier is null) - { - return; - } - var headers = (IHeaderDictionary)carrier; + var headers = (IHeaderDictionary)carrier!; fieldValue = headers[fieldName]; }, out var requestId, out var traceState); - - if (requestId is not null) - { - activity.SetParentId(requestId); - } - if (traceState is not null) - { - activity.TraceStateString = traceState; - } if (!string.IsNullOrEmpty(requestId)) { + activity.SetParentId(requestId); + if (!string.IsNullOrEmpty(traceState)) + { + activity.TraceStateString = traceState; + } var baggage = _propagator.ExtractBaggage(headers, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { - fieldValue = default; fieldValues = default; - if (carrier is null) - { - return; - } - var headers = (IHeaderDictionary)carrier; + var headers = (IHeaderDictionary)carrier!; fieldValue = headers[fieldName]; });