public void WithCancellationNoncancelableNoDeadlockFromSyncContextWithinJTFRun() { var dispatcher = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(dispatcher); var jtc = new JoinableTaskContext(); jtc.Factory.Run(delegate { WithCancellationSyncBlockOnNoncancelableToken(); return(TplExtensions.CompletedTask); }); }
/// <summary> /// Runs a given scenario many times to observe memory characteristics and assert that they can satisfy given conditions. /// </summary> /// <param name="scenario">The delegate to invoke.</param> /// <param name="maxBytesAllocated">The maximum number of bytes allowed to be allocated by one run of the scenario. Use -1 to indicate no limit.</param> /// <param name="iterations">The number of times to invoke <paramref name="scenario"/> in a row before measuring average memory impact.</param> /// <param name="allowedAttempts">The number of times the (scenario * iterations) loop repeats with a failing result before ultimately giving up.</param> protected void CheckGCPressure(Action scenario, int maxBytesAllocated, int iterations = 100, int allowedAttempts = GCAllocationAttempts) { // prime the pump for (int i = 0; i < iterations; i++) { scenario(); } // This test is rather rough. So we're willing to try it a few times in order to observe the desired value. bool passingAttemptObserved = false; for (int attempt = 1; attempt <= allowedAttempts; attempt++) { this.Logger?.WriteLine("Iteration {0}", attempt); long initialMemory = GC.GetTotalMemory(true); for (int i = 0; i < iterations; i++) { scenario(); } long allocated = (GC.GetTotalMemory(false) - initialMemory) / iterations; // If there is a dispatcher sync context, let it run for a bit. // This allows any posted messages that are now obsolete to be released. if (SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { var frame = SingleThreadedSynchronizationContext.NewFrame(); SynchronizationContext.Current.Post(state => frame.Continue = false, null); SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame); } long leaked = (GC.GetTotalMemory(true) - initialMemory) / iterations; this.Logger?.WriteLine("{0} bytes leaked per iteration.", leaked); this.Logger?.WriteLine("{0} bytes allocated per iteration ({1} allowed).", allocated, maxBytesAllocated); if (leaked <= 0 && (maxBytesAllocated == -1 || allocated <= maxBytesAllocated)) { passingAttemptObserved = true; } if (!passingAttemptObserved) { // give the system a bit of cool down time to increase the odds we'll pass next time. GC.Collect(); Thread.Sleep(250); } } Assert.True(passingAttemptObserved); }
public void WithCancellationNoncancelableNoDeadlockFromSyncContext() { var dispatcher = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(dispatcher); var tcs = new TaskCompletionSource <object>(); Task.Run(async delegate { await Task.Delay(AsyncDelay); tcs.SetResult(null); }); ((Task)tcs.Task).WithCancellation(CancellationToken.None).Wait(TestTimeout); }
protected JoinableTaskTestBase(ITestOutputHelper logger) : base(logger) { this.dispatcherContext = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(this.dispatcherContext); this.context = this.CreateJoinableTaskContext(); this.joinableCollection = this.context.CreateCollection(); this.asyncPump = this.context.CreateFactory(this.joinableCollection); this.originalThread = Thread.CurrentThread; this.testFrame = SingleThreadedSynchronizationContext.NewFrame(); // Suppress the assert dialog that appears and causes test runs to hang. Trace.Listeners.OfType <DefaultTraceListener>().Single().AssertUiEnabled = false; }
protected void ExecuteOnDispatcher(Func <Task> action) { Action worker = delegate { var frame = SingleThreadedSynchronizationContext.NewFrame(); Exception failure = null; SynchronizationContext.Current.Post( async _ => { try { await action(); } catch (Exception ex) { failure = ex; } finally { frame.Continue = false; } }, null); SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame); if (failure != null) { ExceptionDispatchInfo.Capture(failure).Throw(); } }; if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { worker(); } else { this.ExecuteOnSTA(() => { if (!SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New()); } worker(); }); } }
public void WithCancellationNoDeadlockFromSyncContext() { var dispatcher = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(dispatcher); var tcs = new TaskCompletionSource <object>(); var cts = new CancellationTokenSource(AsyncDelay / 4); try { ((Task)tcs.Task).WithCancellation(cts.Token).Wait(TestTimeout); Assert.True(false, "Expected OperationCanceledException not thrown."); } catch (AggregateException ex) { ex.Handle(x => x is OperationCanceledException); } }
public void ValueFactoryRequiresMainThreadHeldByOtherSync(bool passJtfToLazyCtor) { var ctxt = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(ctxt); var context = new JoinableTaskContext(); var asyncPump = context.Factory; var originalThread = Thread.CurrentThread; var evt = new AsyncManualResetEvent(); var lazy = new AsyncLazy <object>( async delegate { // It is important that no await appear before this JTF.Run call, since // we're testing that the value factory is not invoked while the AsyncLazy // holds a private lock that would deadlock when called from another thread. asyncPump.Run(async delegate { await asyncPump.SwitchToMainThreadAsync(this.TimeoutToken); }); await Task.Yield(); return(new object()); }, passJtfToLazyCtor ? asyncPump : null); // mix it up to exercise all the code paths in the ctor. var backgroundRequest = Task.Run(async delegate { return(await lazy.GetValueAsync()); }); Thread.Sleep(AsyncDelay); // Give the background thread time to call GetValueAsync(), but it doesn't yield (when the test was written). var foregroundRequest = lazy.GetValueAsync(); var frame = SingleThreadedSynchronizationContext.NewFrame(); var combinedTask = Task.WhenAll(foregroundRequest, backgroundRequest); combinedTask.WithTimeout(UnexpectedTimeout).ContinueWith(_ => frame.Continue = false, TaskScheduler.Default); SingleThreadedSynchronizationContext.PushFrame(ctxt, frame); // Ensure that the test didn't simply timeout, and that the individual tasks did not throw. Assert.True(foregroundRequest.IsCompleted); Assert.True(backgroundRequest.IsCompleted); Assert.Same(foregroundRequest.GetAwaiter().GetResult(), backgroundRequest.GetAwaiter().GetResult()); }
public void ValueFactoryRequiresMainThreadHeldByOtherInJTFRun() { var ctxt = SingleThreadedSynchronizationContext.New(); SynchronizationContext.SetSynchronizationContext(ctxt); var context = new JoinableTaskContext(); var asyncPump = context.Factory; var originalThread = Thread.CurrentThread; var evt = new AsyncManualResetEvent(); var lazy = new AsyncLazy <object>( async delegate { // It is important that no await appear before this JTF.Run call, since // we're testing that the value factory is not invoked while the AsyncLazy // holds a private lock that would deadlock when called from another thread. asyncPump.Run(async delegate { await asyncPump.SwitchToMainThreadAsync(this.TimeoutToken); }); await Task.Yield(); return(new object()); }, asyncPump); var backgroundRequest = Task.Run(async delegate { return(await lazy.GetValueAsync()); }); Thread.Sleep(AsyncDelay); // Give the background thread time to call GetValueAsync(), but it doesn't yield (when the test was written). asyncPump.Run(async delegate { var foregroundValue = await lazy.GetValueAsync(this.TimeoutToken); var backgroundValue = await backgroundRequest; Assert.Same(foregroundValue, backgroundValue); }); }
public void AsyncLazy_CompletesOnThreadWithValueFactory(NamedSyncContexts invokeOn, NamedSyncContexts completeOn) { // Set up various SynchronizationContexts that we may invoke or complete the async method with. var aSyncContext = SingleThreadedSynchronizationContext.New(); var bSyncContext = SingleThreadedSynchronizationContext.New(); var invokeOnSyncContext = invokeOn == NamedSyncContexts.None ? null : invokeOn == NamedSyncContexts.A ? aSyncContext : invokeOn == NamedSyncContexts.B ? bSyncContext : throw new ArgumentOutOfRangeException(nameof(invokeOn)); var completeOnSyncContext = completeOn == NamedSyncContexts.None ? null : completeOn == NamedSyncContexts.A ? aSyncContext : completeOn == NamedSyncContexts.B ? bSyncContext : throw new ArgumentOutOfRangeException(nameof(completeOn)); // Set up a single-threaded SynchronizationContext that we'll invoke the async method within. SynchronizationContext.SetSynchronizationContext(invokeOnSyncContext); var unblockAsyncMethod = new TaskCompletionSource <bool>(); var getValueTask = new AsyncLazy <int>(async delegate { await unblockAsyncMethod.Task.ConfigureAwaitRunInline(); return(Environment.CurrentManagedThreadId); }).GetValueAsync(); var verificationTask = getValueTask.ContinueWith( lazyValue => Environment.CurrentManagedThreadId, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); SynchronizationContext.SetSynchronizationContext(completeOnSyncContext); unblockAsyncMethod.SetResult(true); // Confirm that setting the intermediate task allowed the async method to complete immediately, using our thread to do it. Assert.True(verificationTask.IsCompleted); Assert.Equal(Environment.CurrentManagedThreadId, verificationTask.Result); }
private JoinableTaskContext InitializeJTCAndSC() { SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New()); return(new JoinableTaskContext()); }
/// <summary> /// Runs a given scenario many times to observe memory characteristics and assert that they can satisfy given conditions. /// </summary> /// <param name="scenario">The delegate to invoke.</param> /// <param name="maxBytesAllocated">The maximum number of bytes allowed to be allocated by one run of the scenario. Use -1 to indicate no limit.</param> /// <param name="iterations">The number of times to invoke <paramref name="scenario"/> in a row before measuring average memory impact.</param> /// <param name="allowedAttempts">The number of times the (scenario * iterations) loop repeats with a failing result before ultimately giving up.</param> /// <param name="completeSynchronously"><c>true</c> to synchronously complete instead of yielding.</param> /// <returns>A task that captures the result of the operation.</returns> protected async Task CheckGCPressureAsync(Func <Task> scenario, int maxBytesAllocated, int iterations = 100, int allowedAttempts = GCAllocationAttempts, bool completeSynchronously = false) { const int quietPeriodMaxAttempts = 3; const int shortDelayDuration = 250; const int quietThreshold = 1200; // prime the pump for (int i = 0; i < 2; i++) { await MaybeShouldBeComplete(scenario(), completeSynchronously); } long waitForQuietMemory1, waitForQuietMemory2, waitPeriodAllocations, waitForQuietAttemptCount = 0; do { waitForQuietMemory1 = GC.GetTotalMemory(true); await MaybeShouldBlock(Task.Delay(shortDelayDuration), completeSynchronously); waitForQuietMemory2 = GC.GetTotalMemory(true); waitPeriodAllocations = Math.Abs(waitForQuietMemory2 - waitForQuietMemory1); this.Logger.WriteLine("Bytes allocated during quiet wait period: {0}", waitPeriodAllocations); }while (waitPeriodAllocations > quietThreshold || ++waitForQuietAttemptCount >= quietPeriodMaxAttempts); if (waitPeriodAllocations > quietThreshold) { this.Logger.WriteLine("WARNING: Unable to establish a quiet period."); } // This test is rather rough. So we're willing to try it a few times in order to observe the desired value. bool attemptWithNoLeakObserved = false; bool attemptWithinMemoryLimitsObserved = false; for (int attempt = 1; attempt <= allowedAttempts; attempt++) { this.Logger?.WriteLine("Iteration {0}", attempt); long initialMemory = GC.GetTotalMemory(true); for (int i = 0; i < iterations; i++) { await MaybeShouldBeComplete(scenario(), completeSynchronously); } long allocated = (GC.GetTotalMemory(false) - initialMemory) / iterations; attemptWithinMemoryLimitsObserved |= maxBytesAllocated == -1 || allocated <= maxBytesAllocated; long leaked = long.MaxValue; for (int leakCheck = 0; leakCheck < 3; leakCheck++) { // Allow the message queue to drain. if (completeSynchronously) { // If there is a dispatcher sync context, let it run for a bit. // This allows any posted messages that are now obsolete to be released. if (SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { var frame = SingleThreadedSynchronizationContext.NewFrame(); SynchronizationContext.Current.Post(state => frame.Continue = false, null); SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame); } } else { await Task.Yield(); } leaked = (GC.GetTotalMemory(true) - initialMemory) / iterations; attemptWithNoLeakObserved |= leaked <= 3 * IntPtr.Size; // any real leak would be an object, which is at least this size. if (attemptWithNoLeakObserved) { break; } await MaybeShouldBlock(Task.Delay(shortDelayDuration), completeSynchronously); } this.Logger?.WriteLine("{0} bytes leaked per iteration.", leaked); this.Logger?.WriteLine("{0} bytes allocated per iteration ({1} allowed).", allocated, maxBytesAllocated); if (attemptWithNoLeakObserved && attemptWithinMemoryLimitsObserved) { // Don't keep looping. We got what we needed. break; } // give the system a bit of cool down time to increase the odds we'll pass next time. GC.Collect(); await MaybeShouldBlock(Task.Delay(shortDelayDuration), completeSynchronously); } Assert.True(attemptWithNoLeakObserved, "Leaks observed in every iteration."); Assert.True(attemptWithinMemoryLimitsObserved, "Excess memory allocations in every iteration."); }
protected void PushFrame() { SingleThreadedSynchronizationContext.PushFrame(this.dispatcherContext, this.testFrame); }
protected void ExecuteOnDispatcher(Func <Task> action, bool staRequired = true) { Action worker = delegate { var frame = SingleThreadedSynchronizationContext.NewFrame(); Exception failure = null; SynchronizationContext.Current.Post( async _ => { try { await action(); } catch (Exception ex) { failure = ex; } finally { frame.Continue = false; } }, null); SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame); if (failure != null) { ExceptionDispatchInfo.Capture(failure).Throw(); } }; #if DESKTOP || NETCOREAPP2_0 if ((!staRequired || Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) && SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { worker(); } else { this.ExecuteOnSTA(() => { if (!SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New()); } worker(); }); } #else if (SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current)) { worker(); } else { Task.Run(delegate { SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New()); worker(); }).GetAwaiter().GetResult(); } #endif }