diff --git a/src/Paramore.Brighter/Tasks/BrighterSynchronizationContext.cs b/src/Paramore.Brighter/Tasks/BrighterSynchronizationContext.cs index 9adc5415b..a6abd7ebb 100644 --- a/src/Paramore.Brighter/Tasks/BrighterSynchronizationContext.cs +++ b/src/Paramore.Brighter/Tasks/BrighterSynchronizationContext.cs @@ -33,10 +33,8 @@ namespace Paramore.Brighter.Tasks /// Only uses one thread, so predictable performance, but may have many messages queued. Once queue length exceeds /// buffer size, we will stop reading new work. /// - internal class BrighterSynchronizationContext : SynchronizationContext + public class BrighterSynchronizationContext : SynchronizationContext { - private readonly ExecutionContext? _executionContext; - /// /// Gets the synchronization helper. /// @@ -62,7 +60,6 @@ internal class BrighterSynchronizationContext : SynchronizationContext public BrighterSynchronizationContext(BrighterSynchronizationHelper synchronizationHelper) { SynchronizationHelper = synchronizationHelper; - _executionContext = ExecutionContext.Capture(); } /// @@ -132,8 +129,7 @@ public override void Post(SendOrPostCallback callback, object? state) Debug.IndentLevel = 0; if (callback == null) throw new ArgumentNullException(nameof(callback)); - var ctxt = ExecutionContext.Capture(); - bool queued = SynchronizationHelper.Enqueue(new ContextMessage(callback, state, ctxt), true); + bool queued = SynchronizationHelper.Enqueue(new ContextMessage(callback, state), true); if (queued) return; @@ -141,11 +137,18 @@ public override void Post(SendOrPostCallback callback, object? state) //mostly this seems to be a problem with the task we are running completing, but work is still being queued to the //synchronization context. var contextCallback = new ContextCallback(callback); - if (ctxt != null && ctxt != _executionContext) - ExecuteOnCallersContext(contextCallback, state, ctxt); - else - ExecuteImmediately(contextCallback, state); Debug.WriteLine(string.Empty); + Debug.IndentLevel = 1; + Debug.WriteLine($"BrighterSynchronizationContext: Post Failed to queue {callback.Method.Name} on thread {Thread.CurrentThread.ManagedThreadId}"); + Debug.WriteLine($"BrighterSynchronizationContext: Parent Task {ParentTaskId}"); + Debug.IndentLevel = 0; + + //just execute inline + // current thread already owns the context, so just execute inline to prevent deadlocks + //if (BrighterSynchronizationHelper.Current == SynchronizationHelper) + //SynchronizationHelper.ExecuteImmediately(contextCallback, state); + //else + base.Post(callback, state); } @@ -169,8 +172,7 @@ public override void Send(SendOrPostCallback callback, object? state) } else { - var ctxt = ExecutionContext.Capture(); - var task = SynchronizationHelper.MakeTask(new ContextMessage(callback, state, ctxt)); + var task = SynchronizationHelper.MakeTask(new ContextMessage(callback, state)); if (!task.Wait(Timeout)) // Timeout mechanism throw new TimeoutException("BrighterSynchronizationContext: Send operation timed out."); } diff --git a/src/Paramore.Brighter/Tasks/ContextMessage.cs b/src/Paramore.Brighter/Tasks/ContextMessage.cs index 038648bdf..0804f431f 100644 --- a/src/Paramore.Brighter/Tasks/ContextMessage.cs +++ b/src/Paramore.Brighter/Tasks/ContextMessage.cs @@ -16,11 +16,9 @@ public struct ContextMessage /// /// The callback to execute. /// The state to pass to the callback. - /// The execution context, mainly intended for debugging purposes - public ContextMessage(SendOrPostCallback callback, object? state, ExecutionContext? ctxt) + public ContextMessage(SendOrPostCallback callback, object? state) { Callback = callback; State = state; - Context = ctxt; } } diff --git a/tests/Paramore.Brighter.Core.Tests/Tasks/BrighterSynchronizationContextsTests.cs b/tests/Paramore.Brighter.Core.Tests/Tasks/BrighterSynchronizationContextsTests.cs index 99abe8ee3..92f3108fd 100644 --- a/tests/Paramore.Brighter.Core.Tests/Tasks/BrighterSynchronizationContextsTests.cs +++ b/tests/Paramore.Brighter.Core.Tests/Tasks/BrighterSynchronizationContextsTests.cs @@ -151,26 +151,26 @@ public void Current_WithoutAsyncContext_IsNull() } [Fact] - public void Current_FromAsyncContext_IsAsyncContext() + public void Current_FromBrighterSynchronizationHelper_IsBrighterSynchronizationHelper() { - BrighterSynchronizationHelper observedContext = null; - var context = new BrighterSynchronizationHelper(); + BrighterSynchronizationHelper observedHelper = null; + var helper = new BrighterSynchronizationHelper(); - var task = context.Factory.StartNew( - () => { observedContext = BrighterSynchronizationHelper.Current; }, - context.Factory.CancellationToken, - context.Factory.CreationOptions | TaskCreationOptions.DenyChildAttach, - context.TaskScheduler); + var task = helper.Factory.StartNew( + () => { observedHelper = BrighterSynchronizationHelper.Current; }, + helper.Factory.CancellationToken, + helper.Factory.CreationOptions | TaskCreationOptions.DenyChildAttach, + helper.TaskScheduler); - context.Execute(task); + helper.Execute(task); - observedContext.Should().Be(context); + observedHelper.Should().Be(helper); } [Fact] - public void SynchronizationContextCurrent_FromAsyncContext_IsAsyncContextSynchronizationContext() + public void SynchronizationContextCurrent_FromBrighterSynchronizationHelper_IsBrighterSynchronizationHelperSynchronizationContext() { - System.Threading.SynchronizationContext observedContext = null; + System.Threading.SynchronizationContext? observedContext = null; var context = new BrighterSynchronizationHelper(); var task = context.Factory.StartNew( @@ -362,6 +362,35 @@ public async Task Task_AfterExecute_Runs_On_ThreadPool() threadPoolExceptionRan.Should().BeTrue(); } + + [Fact] + public async Task SynchronizationContextCurrent_FromAsyncContext_PostFromAnotherThread() + { + System.Threading.SynchronizationContext? observedContext = null; + var helper = new BrighterSynchronizationHelper(); + + var task = helper.Factory.StartNew( + () => { observedContext =BrighterSynchronizationContext.Current; }, + helper.Factory.CancellationToken, + helper.Factory.CreationOptions | TaskCreationOptions.DenyChildAttach, + helper.TaskScheduler); + + //this should complete the task + helper.Execute(task); + + //but this simulates us being disposed + observedContext.OperationCompleted(); + + //we may be called on a different thread + int value = 1; + await Task.Run(() => + { + observedContext .Post(_ => value = 2, null); + }); + + value.Should().Be(2); + + } [Fact] public void SynchronizationContext_IsEqualToCopyOfItself()