-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
[Edit 04/07/2022: Use the event as proposed by @noahfalk]
[Edit 04/07/2022: Updates per conversation with @tarekgh ]
A typical implementation of distributed tracing uses an AsyncLocal<T> to track the "span context" of managed threads. When changes to the span context needs to be tracked this is done by using the AsyncLocal<T> constructor that takes the valueChangedHandler parameter. However, with Activity becoming the standard to represent spans, as used by OpenTelemetry, it is impossible to set the value changed handler since the context is tracked via Activity.Current.
cc @tarekgh @open-telemetry/dotnet-instrumentation-approvers
API Proposal
namespace System.Diagnostics
{
public readonly struct ActivityNotification
{
public Activity? Previous { get; }
public Activity? Current { get; }
}
public partial class Activity : IDisposable
{
// Add an event to track updates to the current Activity
public static event EventHandler<ActivityNotification>? CurrentChanged;
}
}API Usage
AsyncLocal<Activity> t_currentActvity = new AsyncLocal<Activity>(ValueChanged);
void Init()
{
Activity.CurrentChanged += OnCurrentActivityChange;
}
void OnCurrentActivityChange(object sender, ActivityNotification notification)
{
t_currentActvity = notification.Current;
}
void ValueChanged(AsyncLocalValueChangedArgs<Activity> args)
{
// Profiler can do whatever it wants with the Activity here. This callback is invoked every time the Activity.Current is set.
}Alternative Designs
Using registration methods instead of an Event:
namespace System.Diagnostics
{
public delegate void ActivityCurrentChangeHandler(Activity? previous, Activity? current);
public partial class Activity : IDisposable
{
public static void RegisterActivityCurrentChangeNotification(ActivityCurrentChangeHandler handler) { }
public static void CancelActivityCurrentChangeNotification(ActivityCurrentChangeHandler handler) { }
}
}Using an Event but using an action that receives only the new current Activity:
namespace System.Diagnostics
{
public partial class Activity
{
public static event Action<Activity?, Activity?>? ActivityChanged;
}
}-
Considered the
AsyncLocal<Activity>backingActivity.Currentto always be created with avalueChangedHandlerand exposing a static method onActivitythat would allow users to add their own handlers if desired. -
Implement the support to add value changed handlers directly in
AsyncLocal<T>this way any other type of context that needs to be shared can leverage the same implementation and expose similar APIs if desired. However, any implementation still needs to expose an API to add handlers.
Risks
Potentially Activity.Current can change very frequently, especially in a server application, so the implementation should be "pay-for-play" as much as possible.