コード例 #1
0
    public void SynchronizationContextCaptured()
    {
        SynchronizationContext?syncContext = SingleThreadedTestSynchronizationContext.New();

        SynchronizationContext.SetSynchronizationContext(syncContext);
        TaskCompletionSource <object?> callbackResult = new TaskCompletionSource <object?>();

        SingleThreadedTestSynchronizationContext.IFrame?frame = SingleThreadedTestSynchronizationContext.NewFrame();
        var callback = new Action <GenericParameterHelper>(
            p =>
        {
            try
            {
                Assert.NotNull(SynchronizationContext.Current);
                callbackResult.SetResult(null);
            }
            catch (Exception e)
            {
                callbackResult.SetException(e);
            }

            frame.Continue = false;
        });
        var progress = new ProgressWithCompletion <GenericParameterHelper>(callback);
        IProgress <GenericParameterHelper> reporter = progress;

        Task.Run(delegate
        {
            reporter.Report(new GenericParameterHelper(1));
        });

        SingleThreadedTestSynchronizationContext.PushFrame(syncContext, frame);
        callbackResult.Task.GetAwaiter().GetResult();
    }
コード例 #2
0
    protected void ExecuteOnDispatcher(Func <Task> action)
    {
        if (!SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
        {
            SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
        }

        SingleThreadedTestSynchronizationContext.IFrame?frame = SingleThreadedTestSynchronizationContext.NewFrame();
        Exception?failure = null;

        SynchronizationContext.Current !.Post(
            async _ =>
        {
            try
            {
                await action();
            }
            catch (Exception ex)
            {
                failure = ex;
            }
            finally
            {
                frame.Continue = false;
            }
        },
            null);

        SingleThreadedTestSynchronizationContext.PushFrame(SynchronizationContext.Current, frame);
        if (failure is object)
        {
            ExceptionDispatchInfo.Capture(failure).Throw();
        }
    }
コード例 #3
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 is null)
        {
            throw new ArgumentNullException(nameof(func));
        }

        SynchronizationContext?prevCtx = SynchronizationContext.Current;

        try
        {
            SynchronizationContext?syncCtx = SingleThreadedTestSynchronizationContext.New();
            SynchronizationContext.SetSynchronizationContext(syncCtx);

            Task?t = func();
            if (t is null)
            {
                throw new InvalidOperationException();
            }

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

            t.GetAwaiter().GetResult();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(prevCtx);
        }
    }
コード例 #4
0
    protected JoinableTaskTestBase(ITestOutputHelper logger)
        : base(logger)
    {
        this.dispatcherContext = SingleThreadedTestSynchronizationContext.New();
        SynchronizationContext.SetSynchronizationContext(this.dispatcherContext);
#pragma warning disable CA2214 // Do not call overridable methods in constructors
        this.context = this.CreateJoinableTaskContext();
#pragma warning restore CA2214 // Do not call overridable methods in constructors
        this.joinableCollection      = this.context.CreateCollection();
        this.asyncPump               = this.context.CreateFactory(this.joinableCollection);
        this.originalThreadManagedId = Environment.CurrentManagedThreadId;
        this.testFrame               = SingleThreadedTestSynchronizationContext.NewFrame();

        // Suppress the assert dialog that appears and causes test runs to hang.
        Trace.Listeners.OfType <DefaultTraceListener>().Single().AssertUiEnabled = false;
    }
コード例 #5
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);
            int[] gcCountBefore = new int[GC.MaxGeneration + 1];
            int[] gcCountAfter  = new int[GC.MaxGeneration + 1];
            long  initialMemory = GC.GetTotalMemory(true);
            GC.TryStartNoGCRegion(8 * 1024 * 1024);
            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                gcCountBefore[i] = GC.CollectionCount(i);
            }

            for (int i = 0; i < iterations; i++)
            {
                await MaybeShouldBeComplete(scenario(), completeSynchronously);
            }

            for (int i = 0; i < gcCountAfter.Length; i++)
            {
                gcCountAfter[i] = GC.CollectionCount(i);
            }

            long allocated = (GC.GetTotalMemory(false) - initialMemory) / iterations;
            GC.EndNoGCRegion();

            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 (SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
                    {
                        SingleThreadedTestSynchronizationContext.IFrame?frame = SingleThreadedTestSynchronizationContext.NewFrame();
                        SynchronizationContext.Current.Post(state => frame.Continue = false, null);
                        SingleThreadedTestSynchronizationContext.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);

            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                Assert.False(gcCountAfter[i] > gcCountBefore[i], $"WARNING: Gen {i} GC occurred {gcCountAfter[i] - gcCountBefore[i]} times during testing. Results are probably totally wrong.");
            }

            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.");
    }