public static async Task TaskDropsExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by captured ExecutionContext, // run a task and wait for it, and then hold on to that task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; await Task.Run(delegate // avoid any issues with the stack keeping the object alive { var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object> { Value = state }; // ensure the object is stored in ExecutionContext t = Task.Run(() => { }); // run a task that'll capture EC al.Value = null; }); await t; // wait for the task method to complete and clear out its state for (int i = 0; i < 2; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } await tcs.Task.TimeoutAfter(60_000); // finalizable object should have been collected and finalized GC.KeepAlive(t); // ensure the object is stored in the state machine }
public static async Task TaskCompletionSourceDoesntCaptureExecutionContext(Func <TaskCompletionSource <int> > tcsFactory) { // Create a finalizable object that'll be referenced by captured ExecutionContext, // create a TCS, and then hold on to that while forcing GCs and finalizers. // We want to make sure that holding on to the resulting TCS doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource <int> t = null; await Task.Run(delegate // avoid any issues with the stack keeping the object alive { var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object> { Value = state }; // ensure the object is stored in ExecutionContext t = tcsFactory(); // create the TCS that shouldn't capture ExecutionContext al.Value = null; }); for (int i = 0; i < 2; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } await tcs.Task.TimeoutAfter(60_000); // finalizable object should have been collected and finalized GC.KeepAlive(t); // ensure the TCS is stored in the state machine }
public static async Task AsyncMethodsDropsStateMachineAndExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by both an async method's // captured ExecutionContext and its state machine, invoke the method, wait for it, // and then hold on to the resulting task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; await Task.Run(delegate // avoid any issues with the stack keeping the object alive, and escape xunit sync ctx { async Task YieldOnceAsync(object s) { await Task.Yield(); GC.KeepAlive(s); // keep s referenced by the state machine } var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object>(args => { // Temporary logging to get more info when the test timeout to look who hold a reference to the finalizer object. string currentValue = args.CurrentValue == null ? "'null'" : "'Object'"; string previousValue = args.PreviousValue == null ? "'null'" : "'Object'"; Console.WriteLine($"AsyncMethodsDropsStateMachineAndExecutionContextUponCompletion: Thread Id: {Thread.CurrentThread.ManagedThreadId} Current Value: {currentValue} Previous Value: {previousValue} ThreadContextChanged: {args.ThreadContextChanged}"); }) { Value = state }; // ensure the object is stored in ExecutionContext t = YieldOnceAsync(state); // ensure the object is stored in the state machine al.Value = null; });
public static async Task AsyncMethodsDropsStateMachineAndExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by both an async method's // captured ExecutionContext and its state machine, invoke the method, wait for it, // and then hold on to the resulting task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; await Task.Run(delegate // avoid any issues with the stack keeping the object alive, and escape xunit sync ctx { async Task YieldOnceAsync(object s) { await Task.Yield(); GC.KeepAlive(s); // keep s referenced by the state machine } var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object> { Value = state }; // ensure the object is stored in ExecutionContext t = YieldOnceAsync(state); // ensure the object is stored in the state machine al.Value = null; });
public static async Task AsyncMethodsDropsStateMachineAndExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by both an async method's // captured ExecutionContext and its state machine, invoke the method, wait for it, // and then hold on to the resulting task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; Thread runner = new Thread(() => { async Task YieldOnceAsync(object s) { await Task.Yield(); GC.KeepAlive(s); // keep s referenced by the state machine } var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult() }; var al = new AsyncLocal <object>() { Value = state }; // ensure the object is stored in ExecutionContext t = YieldOnceAsync(state); // ensure the object is stored in the state machine al.Value = null; }) { IsBackground = true }; runner.Start(); runner.Join(); await t; // wait for the async method to complete and clear out its state await Task.Yield(); // ensure associated state is not still on the stack as part of the antecedent's execution for (int i = 0; i < 2; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } try { await tcs.Task.WaitAsync(TimeSpan.FromSeconds(60)); } catch (Exception e) { Environment.FailFast("Look at the created dump", e); } GC.KeepAlive(t); // ensure the object is stored in the state machine }
public static async Task AsyncMethodsDropsStateMachineAndExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by both an async method's // captured ExecutionContext and its state machine, invoke the method, wait for it, // and then hold on to the resulting task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. bool finalized = false; Task t = null; Thread runner = new Thread(() => { async Task YieldOnceAsync(object s) { await Task.Yield(); GC.KeepAlive(s); // keep s referenced by the state machine } var state = new InvokeActionOnFinalization { Action = () => Volatile.Write(ref finalized, true) }; var al = new AsyncLocal <object>() { Value = state }; // ensure the object is stored in ExecutionContext t = YieldOnceAsync(state); // ensure the object is stored in the state machine al.Value = null; }) { IsBackground = true }; runner.Start(); runner.Join(); await t; // wait for the async method to complete and clear out its state await Task.Yield(); // ensure associated state is not still on the stack as part of the antecedent's execution for (int i = 0; i < 10 && !Volatile.Read(ref finalized); i++) { GC.Collect(); GC.WaitForPendingFinalizers(); await Task.Yield(); } if (!Volatile.Read(ref finalized)) { Environment.FailFast("Look at the created dump"); } GC.KeepAlive(t); // ensure the object is stored in the state machine }
public static async Task TaskDropsExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by captured ExecutionContext, // run a task and wait for it, and then hold on to that task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; Thread runner = new Thread(() => { var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object>() { Value = state }; // ensure the object is stored in ExecutionContext t = Task.Run(() => { }); // run a task that'll capture EC al.Value = null; }) { IsBackground = true }; runner.Start(); runner.Join(); await t; // wait for the task method to complete and clear out its state for (int i = 0; i < 2; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } try { await tcs.Task.TimeoutAfter(60_000); // finalizable object should have been collected and finalized } catch (Exception e) { Environment.FailFast("Look at the created dump", e); } GC.KeepAlive(t); // ensure the object is stored in the state machine }
public static async Task TaskDropsExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by captured ExecutionContext, // run a task and wait for it, and then hold on to that task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); Task t = null; await Task.Run(delegate // avoid any issues with the stack keeping the object alive { var state = new InvokeActionOnFinalization { Action = () => tcs.SetResult(true) }; var al = new AsyncLocal <object>(args => { // Temporary logging to get more info when the test timeout to look who hold a reference to the finalizer object. string currentValue = args.CurrentValue == null ? "'null'" : "'Object'"; string previousValue = args.PreviousValue == null ? "'null'" : "'Object'"; Console.WriteLine($"TaskDropsExecutionContextUponCompletion: Thread Id: {Thread.CurrentThread.ManagedThreadId} Current Value: {currentValue} Previous Value: {previousValue} ThreadContextChanged: {args.ThreadContextChanged}"); }) { Value = state }; // ensure the object is stored in ExecutionContext t = Task.Run(() => { }); // run a task that'll capture EC al.Value = null; }); await t; // wait for the task method to complete and clear out its state for (int i = 0; i < 2; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } try { await tcs.Task.TimeoutAfter(60_000); // finalizable object should have been collected and finalized } catch (Exception e) { Environment.FailFast("Look at the created dump", e); } GC.KeepAlive(t); // ensure the object is stored in the state machine }
public static async Task TaskDropsExecutionContextUponCompletion() { // Create a finalizable object that'll be referenced by captured ExecutionContext, // run a task and wait for it, and then hold on to that task while forcing GCs and finalizers. // We want to make sure that holding on to the resulting Task doesn't keep // that finalizable object alive. bool finalized = false; Task t = null; Thread runner = new Thread(() => { var state = new InvokeActionOnFinalization { Action = () => Volatile.Write(ref finalized, true) }; var al = new AsyncLocal <object>() { Value = state }; // ensure the object is stored in ExecutionContext t = Task.Run(() => { }); // run a task that'll capture EC al.Value = null; }) { IsBackground = true }; runner.Start(); runner.Join(); await t; // wait for the task method to complete and clear out its state for (int i = 0; i < 10 && !Volatile.Read(ref finalized); i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } if (!Volatile.Read(ref finalized)) { Environment.FailFast("Look at the created dump"); } GC.KeepAlive(t); // ensure the object is stored in the state machine }