< Summary

Information
Class: AmbientServices.TraceLifetime
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/AmbientStackTrace.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 11
Uncovered lines: 0
Coverable lines: 11
Total lines: 97
Line coverage: 100%
Branch coverage
50%
Covered branches: 3
Total branches: 6
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
Dispose()50%22100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/AmbientStackTrace.cs

#LineLine coverage
 1using System;
 2using System.Collections.Immutable;
 3using System.IO;
 4using System.Runtime.CompilerServices;
 5using System.Threading;
 6
 7namespace AmbientServices;
 8
 9/// <summary>
 10/// An interface that is used to notify a subscriber about stack trace information updates.
 11/// </summary>
 12/// <remarks>
 13/// <pitch>The push side of ambient stack tracking: implement this to observe the logical stack maintained by <see cref=
 14/// <pledge><see cref="OnStackTraceUpdated"/> is called after each change to the current call context's trace stack, rec
 15/// </remarks>
 16public interface IStackTraceUpdateSink
 17{
 18    /// <summary>
 19    /// Called when the stack trace information is updated.
 20    /// </summary>
 21    /// <param name="trace">The new stack trace information.</param>
 22    void OnStackTraceUpdated(ImmutableStack<string> trace);
 23}
 24
 25/// <summary>
 26/// A "static" class to track the state of the call stack.
 27/// The class isn't really static, as its members are <see cref="AsyncLocal{T}"/> instances, whose contents vary based o
 28/// </summary>
 29/// <remarks>
 30/// <pitch>A <em>logical</em> stack trace for async code: physical stack traces dissolve into state-machine noise across
 31/// <pledge>
 32/// Each call context carries its own stack; <see cref="Trace"/> pushes a caller-identifying frame and the returned obje
 33/// <see cref="Reset"/> replaces the context's stack with a single baseline frame and registers the sink that will be no
 34/// Snapshots handed to the sink are immutable and safe to retain.
 35/// </pledge>
 36/// <plan>Two <see cref="AsyncLocal{T}"/> slots — an <see cref="ImmutableStack{T}"/> of frame strings and the registered
 37/// <priority>
 38/// 1. Costing nothing where it is not used over complete coverage: only explicitly traced frames appear, so untraced co
 39/// 2. Cheap pushes over rich frames: a frame is a small string formatted from compile-time caller information, not a st
 40/// 3. Self-healing over strict scope accounting: a pop restores the whole immutable snapshot captured at push time, so 
 41/// </priority>
 42/// </remarks>
 43public static class AmbientStackTrace
 44{
 45    private static readonly AsyncLocal<IStackTraceUpdateSink> aNotify = new();
 46    private static readonly AsyncLocal<ImmutableStack<string>> aStack = new();
 47
 48    /// <summary>
 49    /// Reset the async-local stack just in case this context has been recycled and something was left in it.
 50    /// Note that the baseline string is registered on the new stack, but notification is not sent to <paramref name="su
 51    /// </summary>
 52    /// <param name="subscriber">A <see cref="IStackTraceUpdateSink"/> that will receive notifications of updates to the
 53    /// <param name="baseline">The baseline string.</param>
 54    public static void Reset(IStackTraceUpdateSink subscriber, string baseline)
 55    {
 56        aNotify.Value = subscriber;
 57        aStack.Value = ImmutableStack<string>.Empty.Push(baseline);
 58    }
 59    /// <summary>
 60    /// Puts the caller member name, caller file path, and caller line number on the trace stack for this context, keepi
 61    /// </summary>
 62    /// <param name="memberName">The caller's member name (filled in automatically).</param>
 63    /// <param name="filePath">The caller's file name (filled in automatically).</param>
 64    /// <param name="lineNumber">The caller's line number (filled in automatically).</param>
 65    /// <returns>An object that will remove the string from the stack when it is disposed.</returns>
 66    public static IDisposable Trace([CallerMemberName] string? memberName = null, [CallerFilePath] string? filePath = nu
 67    {
 68        aStack.Value ??= ImmutableStack<string>.Empty;
 69        return new TraceLifetime(aStack, aNotify, $"at {memberName} in {Path.GetFileName(filePath ?? "")}:line {lineNumb
 70    }
 71}
 72
 73class TraceLifetime : IDisposable
 74{
 75    private readonly ImmutableStack<string> _previousValue;
 76    private readonly AsyncLocal<ImmutableStack<string>> _asyncLocal;
 77    private readonly AsyncLocal<IStackTraceUpdateSink> _notify;
 78
 279    public TraceLifetime(AsyncLocal<ImmutableStack<string>> stack, AsyncLocal<IStackTraceUpdateSink> notify, string str)
 80    {
 281        _asyncLocal = stack;
 282        _notify = notify;
 283        _previousValue = stack.Value ?? ImmutableStack<string>.Empty;
 284        ImmutableStack<string> newValue = _previousValue.Push(str);
 285        stack.Value = newValue;
 286        notify.Value?.OnStackTraceUpdated(newValue);
 287    }
 88
 89    public void Dispose()
 90    {
 291        _asyncLocal.Value = _previousValue;
 292        _notify.Value?.OnStackTraceUpdated(_previousValue);
 293    }
 94}
 95
 96
 97