简体   繁体   中英

AsyncLocal test failing when run alongside other tests

I am trying to create a custom scope class called AuditScope , whereby the current scope can be accessed via AuditScope.Current .

If there are nested scopes, the current scope is the most nested scope.

I want this to be thread safe, so I am using AsyncLocal to ensure the current scope belongs to the current async context and there are no clashes with other requests. This is similar to the TransactionScope class if any of you have come across it.

Here is my scope class:

public sealed class AuditScope : IDisposable
{
    private static readonly AsyncLocal<Stack<AuditScope>> ScopeStack = new();

    public int ExecutedByUserId { get; }

    public AuditScope(int executedByUserId)
    {
        ExecutedByUserId = executedByUserId;

        if (ScopeStack.Value == null)
        {
            ScopeStack.Value = new Stack<AuditScope>();
        }

        ScopeStack.Value.Push(this);
    }

    public static AuditScope? Current
    {
        get
        {
            if (ScopeStack.Value == null || ScopeStack.Value.Count == 0)
            {
                return null;
            }

            return ScopeStack.Value.Peek();
        }
    }

    public void Dispose()
    {
        ScopeStack.Value?.Pop();
    }
}

All my tests are individually passing, however if I run them all at the same time, one test consistently fails:

[Test]
public async Task GivenThreadCreatesScope_AndSecondThreadCreatesScope_WhenCurrentScopeAccessedOnBothThreads_ThenCorrectScopeReturned()
{
    // Arrange
    static async Task createScopeWithLifespan(int lifespanInMilliseconds)
    {
        // This line throws the error, saying it is not null (for the 2000ms scope)
        // No scope has been created yet for this async context, so current should be null
        Assert.IsNull(AuditScope.Current);

        using (var scope = new AuditScope(1))
        {
            // Scope has been created, so current should match
            Assert.AreEqual(scope, AuditScope.Current);

            await Task.Delay(lifespanInMilliseconds);

            // Scope has not been disposed, so current should still match
            Assert.AreEqual(scope, AuditScope.Current);
        }

        // Scope has been disposed, so current should be null
        Assert.IsNull(AuditScope.Current);
    }

    // Act & Assert
    await Task.WhenAll(
        createScopeWithLifespan(1000),
        createScopeWithLifespan(2000));
}

Surely since the using statements are in different contexts, this should work? Why does pass when run on its own, but not when run alongside the other tests?

For completeness, see below for the other tests I am running it alongside, but I seriously doubt they are directly impacting them:

[Test]
public void GivenNoCurrentScope_WhenCurrentScopeAccessed_ThenNull()
{
    // Act
    var result = AuditScope.Current;

    // Arrange
    Assert.Null(result);
}

[Test]
public void GivenScope_WhenScopeDisposed_ThenNull()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
    }

    // Act
    var result = AuditScope.Current;

    // Arrange
    Assert.Null(result);
}

[Test]
public void GivenScopeCreated_WhenCurrentScopeAccessed_ThenScopeReturned()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        // Act
        var result = AuditScope.Current;

        // Arrange
        Assert.NotNull(result);
        Assert.AreEqual(scope, result);
    }
}

[Test]
public void GivenNestedScopeCreated_WhenCurrentScopeAccessed_ThenNestedScopeReturned()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        using (var nestedScope = new AuditScope(2))
        {
            // Act
            var result = AuditScope.Current;

            // Arrange
            Assert.NotNull(result);
            Assert.AreEqual(nestedScope, result);
        }
    }
}

[Test]
public void GivenNestedScopeCreated_WhenNestedScopeDisposed_ThenCurrentScopeRevertsToParent()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        using (var nestedScope = new AuditScope(2))
        {
        }

        // Act
        var result = AuditScope.Current;

        // Arrange
        Assert.NotNull(result);
        Assert.AreEqual(scope, result);
    }
}

事实证明,某处肯定存在参考问题,因为将Stack<AuditScope>替换为ImmutableStack<AuditScope>修复了该问题。

I had the same problem. Like mine, I believe your issue is in your Dispose:

public void Dispose()
{
    ScopeStack.Value?.Pop();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM