Exemple #1
0
        /// <summary>
        /// Runs an asynchronous task synchronously, using just the current thread to execute continuations.
        /// </summary>
        internal static void Run(Func <Task> func)
        {
            if (func == null)
            {
                throw new ArgumentNullException(nameof(func));
            }

            var prevCtx = SynchronizationContext.Current;

            try
            {
                var syncCtx = SingleThreadedSynchronizationContext.New();
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                var t = func();
                if (t == null)
                {
                    throw new InvalidOperationException();
                }

                var frame = SingleThreadedSynchronizationContext.NewFrame();
                t.ContinueWith(_ => { frame.Continue = false; }, TaskScheduler.Default);
                SingleThreadedSynchronizationContext.PushFrame(syncCtx, frame);

                t.GetAwaiter().GetResult();
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(prevCtx);
            }
        }
Exemple #2
0
        /// <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);
        }
        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;
        }
Exemple #4
0
        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();
                });
            }
        }
Exemple #5
0
        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());
        }
Exemple #6
0
        /// <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.");
        }
Exemple #7
0
        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
        }