Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static Activity Current
#if ALLOW_PARTIALLY_TRUSTED_CALLERS
[System.Security.SecuritySafeCriticalAttribute]
#endif
private set {}
set {}
}
}
public abstract partial class DiagnosticSource {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace System.Diagnostics
public partial class Activity
{
/// <summary>
/// Returns the current operation (Activity) for the current thread. This flows
/// Gets or sets the current operation (Activity) for the current thread. This flows
/// across async calls.
/// </summary>
public static Activity Current
Expand All @@ -22,37 +22,44 @@ public static Activity Current
get
{
ObjectHandle activityHandle = (ObjectHandle)CallContext.LogicalGetData(FieldKey);

// Unwrap the Activity if it was set in the same AppDomain (as FieldKey is AppDomain-specific).
if (activityHandle != null)
{
return (Activity)activityHandle.Unwrap();
}
return null;
}

#if ALLOW_PARTIALLY_TRUSTED_CALLERS
[System.Security.SecuritySafeCriticalAttribute]
#endif
private set
set
{
// Applications may implicitly or explicitly call other AppDomains
// that do not have DiagnosticSource DLL, therefore may not be able to resolve Activity type stored in the logical call context.
// To avoid it, we wrap Activity with ObjectHandle.
CallContext.LogicalSetData(FieldKey, new ObjectHandle(value));
if (ValidateSetCurrent)
{
SetCurrent(value);
}
}
}

#region private

private partial class KeyValueListNode
#if ALLOW_PARTIALLY_TRUSTED_CALLERS
[System.Security.SecuritySafeCriticalAttribute]
#endif
private static void SetCurrent(Activity activity)
{
// Applications may implicitly or explicitly call other AppDomains
// that do not have DiagnosticSource DLL, therefore may not be able to resolve Activity type stored in the logical call context.
// To avoid it, we wrap Activity with ObjectHandle.
CallContext.LogicalSetData(FieldKey, new ObjectHandle(activity));
}

// Slot name depends on the AppDomain Id in order to prevent AppDomains to use the same Activity
// Cross AppDomain calls are considered as 'external' i.e. only Activity Id and Baggage should be propagated and
// new Activity should be started for the RPC calls (incoming and outgoing)
private static readonly string FieldKey = $"{typeof(Activity).FullName}_{AppDomain.CurrentDomain.Id}";
#endregion
#endregion //private
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@ namespace System.Diagnostics
public partial class Activity
{
/// <summary>
/// Returns the current operation (Activity) for the current thread. This flows
/// Gets or sets the current operation (Activity) for the current thread. This flows
/// across async calls.
/// </summary>
public static Activity Current
{
get { return s_current.Value; }
private set { s_current.Value = value; }
set
{
if (ValidateSetCurrent(value))
{
SetCurrent(value);
}
}
}

#region private
private static void SetCurrent(Activity activity)
{
s_current.Value = activity;
}

private static readonly AsyncLocal<Activity> s_current = new AsyncLocal<Activity>();
#endregion // private
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Security;
#if ALLOW_PARTIALLY_TRUSTED_CALLERS
using System.Security;
#endif
using System.Threading;

namespace System.Diagnostics
Expand Down Expand Up @@ -308,7 +310,7 @@ public Activity Start()
}

Id = GenerateId();
Current = this;
SetCurrent(this);
}
return this;
}
Expand Down Expand Up @@ -337,7 +339,7 @@ public void Stop()
SetEndTime(GetUtcNow());
}

Current = Parent;
SetCurrent(Parent);
}
}

Expand Down Expand Up @@ -446,6 +448,17 @@ private static unsafe long GetRandomNumber()
return *((long*)&g);
}

private static bool ValidateSetCurrent(Activity activity)
{
bool canSet = activity == null || (activity.Id != null && !activity.isFinished);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are potential races here, but I believe the right answer is to simply tolerate them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, concurrent usage and modification of Activity.Current should be extremely rare and does not look like a valid scenario

if (!canSet)
{
NotifyError(new InvalidOperationException("Trying to set an Activity that is not running"));
}

return canSet;
}

private string _rootId;
private int _currentChildId; // A unique number for all children of this activity.

Expand Down
56 changes: 56 additions & 0 deletions src/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,62 @@ public async Task ActivityCurrentFlowsWithAsyncComplex()
Assert.Same(originalActivity, Activity.Current);
}

/// <summary>
/// Tests that Activity.Current could be set
/// </summary>
[Fact]
public async Task ActivityCurrentSet()
{
Activity activity = new Activity("activity");

// start Activity in the 'child' context
await Task.Run(() => activity.Start());

Assert.Null(Activity.Current);
Activity.Current = activity;
Assert.Same(activity, Activity.Current);
}

/// <summary>
/// Tests that Activity.Current could be set to null
/// </summary>
[Fact]
public void ActivityCurrentSetToNull()
{
Activity started = new Activity("started").Start();

Activity.Current = null;
Assert.Null(Activity.Current);
}

/// <summary>
/// Tests that Activity.Current could not be set to Activity
/// that has not been started yet
/// </summary>
[Fact]
public void ActivityCurrentNotSetToNotStarted()
{
Activity started = new Activity("started").Start();
Activity notStarted = new Activity("notStarted");

Activity.Current = notStarted;
Assert.Same(started, Activity.Current);
}

/// <summary>
/// Tests that Activity.Current could not be set to stopped Activity
/// </summary>
[Fact]
public void ActivityCurrentNotSetToStopped()
{
Activity started = new Activity("started").Start();
Activity stopped = new Activity("stopped").Start();
stopped.Stop();

Activity.Current = stopped;
Assert.Same(started, Activity.Current);
}

private class TestObserver : IObserver<KeyValuePair<string, object>>
{
public string EventName { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@
<Link>Common\System\Net\Configuration.Http.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>