Address the System.Diagnostics Feedback (#35582)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Sun, 3 May 2020 19:18:19 +0000 (12:18 -0700)
committerGitHub <noreply@github.com>
Sun, 3 May 2020 19:18:19 +0000 (12:18 -0700)
* Address the System.Diagnostics Feedback

* Address the feedback of the feedback :-)

* Use lock instead of InterlockedExchange+SpinWait

* Remove System.Linq usage

* More feedback addressing

* Fix compilation

13 files changed:
src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netcoreapp.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netfx.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityEvent.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netcoreapp.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netfx.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityListener.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs
src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs

index bb08e3f5d30f027cd021bc15bdea72b4a5d13b42..6428488b65f0d276836edb8942bdcc521334e5bc 100644 (file)
@@ -169,7 +169,7 @@ namespace System.Diagnostics
         public ActivityEvent(string name, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>? attributes) { throw null; }
         public string Name { get { throw null; } }
         public System.DateTimeOffset Timestamp { get { throw null; } }
-        public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>? Attributes { get { throw null; } }
+        public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>> Attributes { get { throw null; } }
     }
     public readonly struct ActivityContext : System.IEquatable<System.Diagnostics.ActivityContext>
     {
index 379675433ae01e205dadb04fce2356004d5e00be..08952ce1e9038df5b0c262909735771e74607dcb 100644 (file)
   </ItemGroup>
   <ItemGroup Condition=" '$(TargetsNetCoreApp)' == 'true' or '$(TargetFramework)' == 'netstandard2.1'">
     <Compile Include="System\Diagnostics\Activity.GenerateRootId.netcoreapp.cs" />
+    <Compile Include="System\Diagnostics\ActivityContext.netcoreapp.cs" />
+    <Compile Include="System\Diagnostics\ActivityLink.netcoreapp.cs" />
   </ItemGroup>
   <ItemGroup Condition=" '$(TargetsNetCoreApp)' != 'true' and '$(TargetFramework)' != 'netstandard2.1' and '$(TargetFramework)' != 'netstandard1.1'">
     <Compile Include="System\Diagnostics\Activity.GenerateRootId.netfx.cs" />
+    <Compile Include="System\Diagnostics\ActivityContext.netfx.cs" />
+    <Compile Include="System\Diagnostics\ActivityLink.netfx.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsNetFx)' == 'true'">
     <Compile Include="System\Diagnostics\HttpHandlerDiagnosticListener.cs" />
index 0129c66e0af7ee79f8a0f2fde4c852bb6767f62f..8d7fffa0203572c5fe96391971cc779a0a720a91 100644 (file)
@@ -37,7 +37,7 @@ namespace System.Diagnostics
         private static readonly IEnumerable<ActivityLink> s_emptyLinks = new ActivityLink[0];
         private static readonly IEnumerable<ActivityEvent> s_emptyEvents = new ActivityEvent[0];
 #pragma warning restore CA1825
-        private static ActivitySource s_defaultSource = new ActivitySource(string.Empty);
+        private static readonly ActivitySource s_defaultSource = new ActivitySource(string.Empty);
 
         private const byte ActivityTraceFlagsIsSet = 0b_1_0000000; // Internal flag to indicate if flags have been set
         private const int RequestIdMaxLength = 1024;
@@ -74,15 +74,15 @@ namespace System.Diagnostics
 
         private byte _w3CIdFlags;
 
-        private LinkedListNode<KeyValuePair<string, string?>>? _tags;
-        private LinkedListNode<KeyValuePair<string, string?>>? _baggage;
-        private LinkedListNode<ActivityLink>? _links;
-        private LinkedListNode<ActivityEvent>? _events;
+        private LinkedList<KeyValuePair<string, string?>>? _tags;
+        private LinkedList<KeyValuePair<string, string?>>? _baggage;
+        private LinkedList<ActivityLink>? _links;
+        private LinkedList<ActivityEvent>? _events;
         private ConcurrentDictionary<string, object>? _customProperties;
         private string? _displayName;
 
         /// <summary>
-        /// Kind describes the relationship between the Activity, its parents, and its children in a Trace.
+        /// Gets the relationship between the Activity, its parents, and its children in a Trace.
         /// </summary>
         public ActivityKind Kind { get; private set; } = ActivityKind.Internal;
 
@@ -94,21 +94,21 @@ namespace System.Diagnostics
         /// </summary>
         public string OperationName { get; } = null!;
 
-        /// <summary>
-        /// DisplayName is name mainly intended to be used in the UI and not necessary has to be
-        /// same as OperationName.
-        /// </summary>
+        /// <summary>Gets or sets the display name of the Activity</summary>
+        /// <remarks>
+        /// DisplayName is intended to be used in a user interface and need not be the same as OperationName.
+        /// </remarks>
         public string DisplayName
         {
             get => _displayName ?? OperationName;
-            set => _displayName = value;
+            set => _displayName = value ?? throw new ArgumentNullException(nameof(value));
         }
 
-        /// <summary>
-        /// Get the ActivitySource object associated with this Activity.
-        /// All Activities created from public constructors will have a singleton source where the source name is empty string.
-        /// Otherwise, the source will be holding the object that created the Activity through ActivitySource.StartActivity.
-        /// </summary>
+        /// <summary>Get the ActivitySource object associated with this Activity.</summary>
+        /// <remarks>
+        /// All Activities created from public constructors will have a singleton source where the source name is an empty string.
+        /// Otherwise, the source will hold the object that created the Activity through ActivitySource.StartActivity.
+        /// </remarks>
         public ActivitySource Source { get; private set; }
 
         /// <summary>
@@ -238,29 +238,13 @@ namespace System.Diagnostics
 
         /// <summary>
         /// Tags are string-string key-value pairs that represent information that will
-        /// be logged along with the Activity to the logging system.   This information
+        /// be logged along with the Activity to the logging system. This information
         /// however is NOT passed on to the children of this activity.
         /// </summary>
         /// <seealso cref="Baggage"/>
         public IEnumerable<KeyValuePair<string, string?>> Tags
         {
-            get
-            {
-                LinkedListNode<KeyValuePair<string, string?>>? tags = _tags;
-                return tags != null ?
-                    Iterate(tags) :
-                    s_emptyBaggageTags;
-
-                static IEnumerable<KeyValuePair<string, string?>> Iterate(LinkedListNode<KeyValuePair<string, string?>>? tags)
-                {
-                    do
-                    {
-                        yield return tags!.Value;
-                        tags = tags.Next;
-                    }
-                    while (tags != null);
-                }
-            }
+            get => _tags != null ? _tags.Enumerate() : s_emptyBaggageTags;
         }
 
         /// <summary>
@@ -269,21 +253,7 @@ namespace System.Diagnostics
         /// </summary>
         public IEnumerable<ActivityEvent> Events
         {
-            get
-            {
-                LinkedListNode<ActivityEvent>? events = _events;
-                return events != null ? Iterate(events) : s_emptyEvents;
-
-                static IEnumerable<ActivityEvent> Iterate(LinkedListNode<ActivityEvent>? events)
-                {
-                    do
-                    {
-                        yield return events!.Value;
-                        events = events.Next;
-                    }
-                    while (events != null);
-                }
-            }
+            get => _events != null ? _events.Enumerate() : s_emptyEvents;
         }
 
         /// <summary>
@@ -292,21 +262,7 @@ namespace System.Diagnostics
         /// </summary>
         public IEnumerable<ActivityLink> Links
         {
-            get
-            {
-                LinkedListNode<ActivityLink>? links = _links;
-                return links != null ? Iterate(links) : s_emptyLinks;
-
-                static IEnumerable<ActivityLink> Iterate(LinkedListNode<ActivityLink>? links)
-                {
-                    do
-                    {
-                        yield return links!.Value;
-                        links = links.Next;
-                    }
-                    while (links != null);
-                }
-            }
+            get => _links != null ? _links.Enumerate() : s_emptyLinks;
         }
 
         /// <summary>
@@ -336,14 +292,16 @@ namespace System.Diagnostics
                     Debug.Assert(activity != null);
                     do
                     {
-                        for (LinkedListNode<KeyValuePair<string, string?>>? baggage = activity._baggage; baggage != null; baggage = baggage.Next)
+                        if (activity._baggage != null)
                         {
-                            yield return baggage.Value;
+                            for (LinkedListNode<KeyValuePair<string, string?>>? current = activity._baggage.First; current != null; current = current.Next)
+                            {
+                                yield return current.Value;
+                            }
                         }
 
                         activity = activity.Parent;
-                    }
-                    while (activity != null);
+                    } while (activity != null);
                 }
             }
         }
@@ -390,13 +348,12 @@ namespace System.Diagnostics
         /// <returns>'this' for convenient chaining</returns>
         public Activity AddTag(string key, string? value)
         {
-            LinkedListNode<KeyValuePair<string, string?>>? currentTags = _tags;
-            LinkedListNode<KeyValuePair<string, string?>> newTags = new LinkedListNode<KeyValuePair<string, string?>>(new KeyValuePair<string, string?>(key, value));
-            do
+            KeyValuePair<string, string?> kvp = new KeyValuePair<string, string?>(key, value);
+
+            if (_tags != null || Interlocked.CompareExchange(ref _tags, new LinkedList<KeyValuePair<string, string?>>(kvp), null) != null)
             {
-                newTags.Next = currentTags;
-                currentTags = Interlocked.CompareExchange(ref _tags, newTags, currentTags);
-            } while (!ReferenceEquals(newTags.Next, currentTags));
+                _tags.Add(kvp);
+            }
 
             return this;
         }
@@ -408,13 +365,10 @@ namespace System.Diagnostics
         /// <returns>'this' for convenient chaining</returns>
         public Activity AddEvent(ActivityEvent e)
         {
-            LinkedListNode<ActivityEvent>? currentEvents = _events;
-            LinkedListNode<ActivityEvent> newEvents = new LinkedListNode<ActivityEvent>(e);
-            do
+            if (_events != null || Interlocked.CompareExchange(ref _events, new LinkedList<ActivityEvent>(e), null) != null)
             {
-                newEvents.Next = currentEvents;
-                currentEvents = Interlocked.CompareExchange(ref _events, newEvents, currentEvents);
-            } while (!ReferenceEquals(newEvents.Next, currentEvents));
+                _events.Add(e);
+            }
 
             return this;
         }
@@ -430,14 +384,12 @@ namespace System.Diagnostics
         /// <returns>'this' for convenient chaining</returns>
         public Activity AddBaggage(string key, string? value)
         {
-            LinkedListNode<KeyValuePair<string, string?>>? currentBaggage = _baggage;
-            LinkedListNode<KeyValuePair<string, string?>> newBaggage = new LinkedListNode<KeyValuePair<string, string?>>(new KeyValuePair<string, string?>(key, value));
+            KeyValuePair<string, string?> kvp = new KeyValuePair<string, string?>(key, value);
 
-            do
+            if (_baggage != null || Interlocked.CompareExchange(ref _baggage, new LinkedList<KeyValuePair<string, string?>>(kvp), null) != null)
             {
-                newBaggage.Next = currentBaggage;
-                currentBaggage = Interlocked.CompareExchange(ref _baggage, newBaggage, currentBaggage);
-            } while (!ReferenceEquals(newBaggage.Next, currentBaggage));
+                _baggage.Add(kvp);
+            }
 
             return this;
         }
@@ -938,17 +890,23 @@ namespace System.Diagnostics
 
             if (links != null)
             {
-                foreach (ActivityLink link in links)
+                using (IEnumerator<ActivityLink> enumerator = links.GetEnumerator())
                 {
-                     activity._links = new LinkedListNode<ActivityLink>(link, activity._links);
+                    if (enumerator.MoveNext())
+                    {
+                        activity._links = new LinkedList<ActivityLink>(enumerator);
+                    }
                 }
             }
 
             if (tags != null)
             {
-                foreach (KeyValuePair<string, string?> tag in tags)
+                using (IEnumerator<KeyValuePair<string, string?>> enumerator = tags.GetEnumerator())
                 {
-                    activity._tags = new LinkedListNode<KeyValuePair<string, string?>>(tag, activity._tags);
+                    if (enumerator.MoveNext())
+                    {
+                        activity._tags = new LinkedList<KeyValuePair<string, string?>>(enumerator);
+                    }
                 }
             }
 
@@ -1191,21 +1149,56 @@ namespace System.Diagnostics
             private set => _state = (_state & ~State.FormatFlags) | (State)((byte)value & (byte)State.FormatFlags);
         }
 
-        /// <summary>
-        /// Having our own key-value linked list allows us to be more efficient
-        /// </summary>
         private partial class LinkedListNode<T>
         {
             public LinkedListNode(T value) => Value = value;
-            public LinkedListNode(T value, LinkedListNode<T>? next)
-            {
-                Value = value;
-                Next = next;
-            }
             public T Value;
             public LinkedListNode<T>? Next;
         }
 
+        // We are not using the public LinkedList<T> because we need to ensure thread safety operation on the list.
+        private class LinkedList<T>
+        {
+            private LinkedListNode<T> _first;
+            private LinkedListNode<T> _last;
+
+            public LinkedList(T firstValue) => _last = _first = new LinkedListNode<T>(firstValue);
+
+            public LinkedList(IEnumerator<T> e)
+            {
+                _last = _first = new LinkedListNode<T>(e.Current);
+
+                while (e.MoveNext())
+                {
+                    _last.Next = new LinkedListNode<T>(e.Current);
+                    _last = _last.Next;
+                }
+            }
+
+            public LinkedListNode<T> First => _first;
+
+            public void Add(T value)
+            {
+                LinkedListNode<T> newNode = new LinkedListNode<T>(value);
+
+                lock (_first)
+                {
+                    _last.Next = newNode;
+                    _last = newNode;
+                }
+            }
+
+            public IEnumerable<T> Enumerate()
+            {
+                LinkedListNode<T>? current = _first;
+                do
+                {
+                    yield return current.Value;
+                    current = current.Next;
+                } while (current != null);
+            }
+        }
+
         [Flags]
         private enum State : byte
         {
index e01cf46f852e2a7bef3396230e6ace5a328e137b..9c242fd87e334f52923d53206a98c79bd3ae6c5f 100644 (file)
@@ -10,7 +10,7 @@ namespace System.Diagnostics
     /// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
     /// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
     /// </summary>
-    public readonly struct ActivityContext : IEquatable<ActivityContext>
+    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
     {
         /// <summary>
         /// Construct a new object of ActivityContext.
@@ -44,12 +44,12 @@ namespace System.Diagnostics
         public ActivitySpanId SpanId { get; }
 
         /// <summary>
-        /// The flags for the details about the trace.
+        /// These flags are defined by the W3C standard along with the ID for the activity.
         /// </summary>
         public ActivityTraceFlags TraceFlags { get; }
 
         /// <summary>
-        /// system-specific configuration data.
+        /// Holds the W3C 'tracestate' header as a string.
         /// </summary>
         public string? TraceState { get; }
 
@@ -58,22 +58,5 @@ namespace System.Diagnostics
         public override bool Equals(object? obj) => (obj is ActivityContext context) ? Equals(context) : false;
         public static bool operator ==(ActivityContext left, ActivityContext right) => left.Equals(right);
         public static bool operator !=(ActivityContext left, ActivityContext right) => !(left == right);
-
-        public override int GetHashCode()
-        {
-            if (this == default)
-                return 0;
-
-            // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
-            // on the extensions package. Considering this simple type and hashing is not expected to be used much, we are implementing
-            // the hashing manually.
-            int hash = 5381;
-            hash = ((hash << 5) + hash) + TraceId.GetHashCode();
-            hash = ((hash << 5) + hash) + SpanId.GetHashCode();
-            hash = ((hash << 5) + hash) + (int) TraceFlags;
-            hash = ((hash << 5) + hash) + (TraceState == null ? 0 : TraceState.GetHashCode());
-
-            return hash;
-        }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netcoreapp.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netcoreapp.cs
new file mode 100644 (file)
index 0000000..ea94205
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+    /// <summary>
+    /// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
+    /// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
+    /// </summary>
+    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
+    {
+        public override int GetHashCode() => HashCode.Combine(TraceId, SpanId, TraceFlags, TraceState);
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netfx.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.netfx.cs
new file mode 100644 (file)
index 0000000..a84bad6
--- /dev/null
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+    /// <summary>
+    /// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
+    /// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
+    /// </summary>
+    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
+    {
+        public override int GetHashCode()
+        {
+            if (this == default)
+                return 0;
+
+            // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
+            // on the extensions package. Considering this simple type and hashing is not expected to be used much, we are implementing
+            // the hashing manually.
+            int hash = 5381;
+            hash = ((hash << 5) + hash) + TraceId.GetHashCode();
+            hash = ((hash << 5) + hash) + SpanId.GetHashCode();
+            hash = ((hash << 5) + hash) + (int) TraceFlags;
+            hash = ((hash << 5) + hash) + (TraceState == null ? 0 : TraceState.GetHashCode());
+
+            return hash;
+        }
+    }
+}
\ No newline at end of file
index 12836bfc30ee3b2745613fdda34a7aec64316ccf..27bc3b0ab2367a2147679d5a1b8d9ce505662e10 100644 (file)
@@ -36,11 +36,8 @@ namespace System.Diagnostics
         /// </summary>
         /// <param name="name">Event name.</param>
         /// <param name="attributes">Event attributes.</param>
-        public ActivityEvent(string name, IEnumerable<KeyValuePair<string, object>>? attributes)
+        public ActivityEvent(string name, IEnumerable<KeyValuePair<string, object>>? attributes) : this(name, default, attributes)
         {
-            this.Name = name ?? string.Empty;
-            this.Attributes = attributes ?? s_emptyAttributes;
-            this.Timestamp = DateTimeOffset.UtcNow;
         }
 
         /// <summary>
@@ -51,9 +48,9 @@ namespace System.Diagnostics
         /// <param name="attributes">Event attributes.</param>
         public ActivityEvent(string name, DateTimeOffset timestamp, IEnumerable<KeyValuePair<string, object>>? attributes)
         {
-            this.Name = name ?? string.Empty;
-            this.Attributes = attributes ?? s_emptyAttributes;
-            this.Timestamp = timestamp != default ? timestamp : DateTimeOffset.UtcNow;
+            Name = name ?? string.Empty;
+            Attributes = attributes ?? s_emptyAttributes;
+            Timestamp = timestamp != default ? timestamp : DateTimeOffset.UtcNow;
         }
 
         /// <summary>
@@ -69,6 +66,6 @@ namespace System.Diagnostics
         /// <summary>
         /// Gets the collection of attributes associated with the event.
         /// </summary>
-        public IEnumerable<KeyValuePair<string, object>>? Attributes { get; }
+        public IEnumerable<KeyValuePair<string, object>> Attributes { get; }
     }
 }
\ No newline at end of file
index bea45b55bd1343e4a13c3f04a2e6b9fa61f62900..920459e5083548d9b64dcb4a3cd195b538045a17 100644 (file)
@@ -12,7 +12,7 @@ namespace System.Diagnostics
     /// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
     /// each representing a single incoming item being processed in the batch.
     /// </summary>
-    public readonly struct ActivityLink  : IEquatable<ActivityLink>
+    public readonly partial struct ActivityLink  : IEquatable<ActivityLink>
     {
         /// <summary>
         /// Construct a new <see cref="ActivityLink"/> object which can be linked to an Activity object.
@@ -46,30 +46,5 @@ namespace System.Diagnostics
         public bool Equals(ActivityLink value) => Context == value.Context && value.Attributes == Attributes;
         public static bool operator ==(ActivityLink left, ActivityLink right) => left.Equals(right);
         public static bool operator !=(ActivityLink left, ActivityLink right) => !left.Equals(right);
-
-        public override int GetHashCode()
-        {
-            if (this == default)
-                return 0;
-
-            // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
-            // on the extensions package. Considering this simple type and hashing is not expected to be used, we are implementing
-            // the hashing manually.
-            int hash = 5381;
-            hash = ((hash << 5) + hash) + this.Context.GetHashCode();
-            if (Attributes != null)
-            {
-                foreach (KeyValuePair<string, object> kvp in Attributes)
-                {
-                    hash = ((hash << 5) + hash) + kvp.Key.GetHashCode();
-                    if (kvp.Value != null)
-                    {
-                        hash = ((hash << 5) + hash) + kvp.Value.GetHashCode();
-                    }
-                }
-            }
-
-            return hash;
-        }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netcoreapp.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netcoreapp.cs
new file mode 100644 (file)
index 0000000..62f1af7
--- /dev/null
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics
+{
+    /// <summary>
+    /// Activity may be linked to zero or more other <see cref="ActivityContext"/> that are causally related.
+    /// Links can point to ActivityContexts inside a single Trace or across different Traces.
+    /// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
+    /// each representing a single incoming item being processed in the batch.
+    /// </summary>
+    public readonly partial struct ActivityLink  : IEquatable<ActivityLink>
+    {
+        public override int GetHashCode()
+        {
+            HashCode hashCode = default;
+            hashCode.Add(Context);
+            if (Attributes != null)
+            {
+                foreach (KeyValuePair<string, object> kvp in Attributes)
+                {
+                    hashCode.Add(kvp.Key);
+                    hashCode.Add(kvp.Value);
+                }
+            }
+            return hashCode.ToHashCode();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netfx.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.netfx.cs
new file mode 100644 (file)
index 0000000..678e1d7
--- /dev/null
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics
+{
+    /// <summary>
+    /// Activity may be linked to zero or more other <see cref="ActivityContext"/> that are causally related.
+    /// Links can point to ActivityContexts inside a single Trace or across different Traces.
+    /// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
+    /// each representing a single incoming item being processed in the batch.
+    /// </summary>
+    public readonly partial struct ActivityLink  : IEquatable<ActivityLink>
+    {
+        public override int GetHashCode()
+        {
+            if (this == default)
+                return 0;
+
+            // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
+            // on the extensions package. Considering this simple type and hashing is not expected to be used, we are implementing
+            // the hashing manually.
+            int hash = 5381;
+            hash = ((hash << 5) + hash) + this.Context.GetHashCode();
+            if (Attributes != null)
+            {
+                foreach (KeyValuePair<string, object> kvp in Attributes)
+                {
+                    hash = ((hash << 5) + hash) + kvp.Key.GetHashCode();
+                    if (kvp.Value != null)
+                    {
+                        hash = ((hash << 5) + hash) + kvp.Value.GetHashCode();
+                    }
+                }
+            }
+            return hash;
+        }
+    }
+}
\ No newline at end of file
index 47703a5e33bc091a8336fe22f3d81fdd17881ac0..5c07128a2d0c1464bb42aa48b867a8be61504bcc 100644 (file)
@@ -21,7 +21,6 @@ namespace System.Diagnostics
         /// </summary>
         public ActivityListener()
         {
-
         }
 
         /// <summary>
index 673111b28552220c5aef7657690867ccccdd6c16..dcf750c32b07856b2ac220fe8522c6259ad23c25 100644 (file)
@@ -9,12 +9,10 @@ namespace System.Diagnostics
 {
     public sealed class ActivitySource : IDisposable
     {
-        private static SynchronizedList<ActivitySource> s_activeSources = new SynchronizedList<ActivitySource>();
-        private static SynchronizedList<ActivityListener> s_allListeners = new SynchronizedList<ActivityListener>();
+        private static readonly SynchronizedList<ActivitySource> s_activeSources = new SynchronizedList<ActivitySource>();
+        private static readonly SynchronizedList<ActivityListener> s_allListeners = new SynchronizedList<ActivityListener>();
         private SynchronizedList<ActivityListener>? _listeners;
 
-        private ActivitySource() { throw new InvalidOperationException(); }
-
         /// <summary>
         /// Construct an ActivitySource object with the input name
         /// </summary>
@@ -250,10 +248,10 @@ namespace System.Diagnostics
     // SynchronizedList<T> is a helper collection which ensure thread safety on the collection
     // and allow enumerating the collection items and execute some action on the enumerated item and can detect any change in the collection
     // during the enumeration which force restarting the enumeration again.
-    // Causion: We can have the action executed on the same item more than once which is ok in our scenarios.
+    // Caution: We can have the action executed on the same item more than once which is ok in our scenarios.
     internal class SynchronizedList<T>
     {
-        private List<T> _list;
+        private readonly List<T> _list;
         private uint _version;
 
         public SynchronizedList() => _list = new List<T>();
index 4151e6ba16ffd556db8e9137498c73dc92fc1d15..11dd57028538f4520f1d88c97a0d806ab9cface6 100644 (file)
@@ -85,9 +85,9 @@ namespace System.Diagnostics.Tests
                     Assert.Equal("world", anotherActivity.GetBaggageItem("hello"));
                     Assert.Equal(4, anotherActivity.Baggage.Count());
                     Assert.Equal(new KeyValuePair<string, string>("hello", "world"), anotherActivity.Baggage.First());
-                    Assert.Equal(new KeyValuePair<string, string>(Key + 2, Value + 2), anotherActivity.Baggage.Skip(1).First());
+                    Assert.Equal(new KeyValuePair<string, string>(Key + 0, Value + 0), anotherActivity.Baggage.Skip(1).First());
                     Assert.Equal(new KeyValuePair<string, string>(Key + 1, Value + 1), anotherActivity.Baggage.Skip(2).First());
-                    Assert.Equal(new KeyValuePair<string, string>(Key + 0, Value + 0), anotherActivity.Baggage.Skip(3).First());
+                    Assert.Equal(new KeyValuePair<string, string>(Key + 2, Value + 2), anotherActivity.Baggage.Skip(3).First());
                 }
                 finally
                 {
@@ -116,8 +116,8 @@ namespace System.Diagnostics.Tests
                 Assert.Equal(activity, activity.AddTag(Key + i, Value + i));
                 List<KeyValuePair<string, string>> tags = activity.Tags.ToList();
                 Assert.Equal(i + 1, tags.Count);
-                Assert.Equal(tags[tags.Count - i - 1].Key, Key + i);
-                Assert.Equal(tags[tags.Count - i - 1].Value, Value + i);
+                Assert.Equal(tags[i].Key, Key + i);
+                Assert.Equal(tags[i].Value, Value + i);
             }
         }
 
@@ -1411,12 +1411,12 @@ namespace System.Diagnostics.Tests
             Assert.True(object.ReferenceEquals(activity, activity.AddEvent(new ActivityEvent("Event2", ts2))));
 
             Assert.Equal(2, activity.Events.Count());
-            Assert.Equal("Event2", activity.Events.ElementAt(0).Name);
-            Assert.Equal(ts2, activity.Events.ElementAt(0).Timestamp);
+            Assert.Equal("Event1", activity.Events.ElementAt(0).Name);
+            Assert.Equal(ts1, activity.Events.ElementAt(0).Timestamp);
             Assert.Equal(0, activity.Events.ElementAt(0).Attributes.Count());
 
-            Assert.Equal("Event1", activity.Events.ElementAt(1).Name);
-            Assert.Equal(ts1, activity.Events.ElementAt(1).Timestamp);
+            Assert.Equal("Event2", activity.Events.ElementAt(1).Name);
+            Assert.Equal(ts2, activity.Events.ElementAt(1).Timestamp);
             Assert.Equal(0, activity.Events.ElementAt(1).Attributes.Count());
         }