| File: CoordinatorServer.DrainableTimer.cs | Web Access |
| Project: MSBuild.Coordinator.csproj (MSBuild.Coordinator) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.Build.Coordinator; internal sealed partial class CoordinatorServer { /// <summary> /// Wraps a <see cref="Timer"/> with separate operations to stop future callbacks and drain callbacks already in progress. /// </summary> private sealed class DrainableTimer : IDisposable { // Running -> StopRequested -> Disposing -> Disposed private const int Running = 0; private const int StopRequested = 1; private const int Disposing = 2; private const int Disposed = 3; private readonly TimerCallback _callback; // Timer.Dispose(WaitHandle) signals this after callbacks already in progress have completed. private readonly ManualResetEvent _callbacksDrained = new(false); private readonly object? _state; private readonly Timer _timer; private int _disposeState; /// <summary> /// Indicates whether new callback execution should be suppressed. /// </summary> private bool IsStoppingOrDisposed => Volatile.Read(ref _disposeState) != Running; /// <summary> /// Creates a timer that invokes the specified callback. /// </summary> /// <param name="callback">The callback to invoke.</param> /// <param name="state">The state supplied to <paramref name="callback"/>.</param> /// <param name="dueTime">The delay before the first callback, in milliseconds.</param> /// <param name="period">The interval between callbacks, in milliseconds.</param> public DrainableTimer(TimerCallback callback, object? state, int dueTime, int period) { _callback = callback; _state = state; _timer = new Timer( static timerState => ((DrainableTimer)timerState!).InvokeCallback(), state: this, dueTime, period); } /// <summary> /// Stops future callbacks without waiting for callbacks already in progress. /// </summary> /// <remarks> /// This method does not block and may be called while the timer callback is running. /// Call <see cref="Dispose"/> later to wait for that callback to complete. /// </remarks> public void Stop() { if (Interlocked.CompareExchange(ref _disposeState, StopRequested, Running) == Running) { _timer.Dispose(_callbacksDrained); } } /// <summary> /// Stops future callbacks and waits for callbacks already in progress to complete. /// </summary> /// <remarks> /// This method must not be called from the timer callback because it waits for that callback to complete. /// Use <see cref="Stop"/> when stopping the timer from a callback execution path. /// </remarks> public void Dispose() { Stop(); if (Interlocked.CompareExchange(ref _disposeState, Disposing, StopRequested) != StopRequested) { return; } try { _callbacksDrained.WaitOne(); } finally { _callbacksDrained.Dispose(); Volatile.Write(ref _disposeState, Disposed); } } private void InvokeCallback() { if (IsStoppingOrDisposed) { return; } _callback(_state); } } }