Skip to content

Commit

Permalink
Merge pull request #606 from leeoades/bugfix/really-guarantee-sync-co…
Browse files Browse the repository at this point in the history
…ntext-lost

Created a custom awaiter that guarantees that we complete on a different threadpool thread.
  • Loading branch information
crozone authored Dec 3, 2024
2 parents 7223486 + 4941336 commit 94867a0
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 5 deletions.
2 changes: 1 addition & 1 deletion test/Stateless.Tests/Stateless.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
Expand Down
28 changes: 24 additions & 4 deletions test/Stateless.Tests/SynchronizationContextFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -41,15 +43,16 @@ private void CaptureSyncContext()

private async Task LoseSyncContext()
{
await Task.Run(() => { }).ConfigureAwait(false); // Switch synchronization context and continue
await new CompletesOnDifferentThreadAwaitable(); // Switch synchronization context and continue
Assert.NotEqual(_customSynchronizationContext, SynchronizationContext.Current);
}

/// <summary>
/// Tests capture the SynchronizationContext at various points through out their execution.
/// This asserts that every capture is the expected SynchronizationContext instance and that is hasn't been lost.
/// Tests capture the SynchronizationContext at various points throughout their execution.
/// This asserts that every capture is the expected SynchronizationContext instance and that it hasn't been lost.
/// </summary>
/// <param name="numberOfExpectedCalls">Ensure that we have the expected number of captures</param>
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private void AssertSyncContextAlwaysRetained(int numberOfExpectedCalls)
{
Assert.Equal(numberOfExpectedCalls, _capturedSyncContext.Count);
Expand Down Expand Up @@ -154,7 +157,7 @@ public async Task Multiple_Deactivations_should_retain_SyncContext()

// ASSERT
AssertSyncContextAlwaysRetained(3);
}
}

[Fact]
public async Task Multiple_OnEntry_should_retain_SyncContext()
Expand Down Expand Up @@ -338,4 +341,21 @@ public async Task InternalTransition_firing_a_sync_action_should_retain_SyncCont
// ASSERT
AssertSyncContextAlwaysRetained(1);
}

private class CompletesOnDifferentThreadAwaitable
{
public CompletesOnDifferentThreadAwaiter GetAwaiter() => new();

internal class CompletesOnDifferentThreadAwaiter : INotifyCompletion
{
public void GetResult() { }

public bool IsCompleted => false;

public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(_ => continuation());
}
}
}
}

0 comments on commit 94867a0

Please sign in to comment.