File: Timer.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.TimeProvider.Testing\Microsoft.Extensions.TimeProvider.Testing.csproj (Microsoft.Extensions.TimeProvider.Testing)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
 
namespace Microsoft.Extensions.Time.Testing;
 
// This implements the timer abstractions and is a thin wrapper around a waiter object.
// The main role of this type is to create the waiter, add it to the waiter list, and ensure it gets
// removed from the waiter list when the dispose is disposed or collected.
internal sealed class Timer : ITimer
{
    private const uint MaxSupportedTimeout = 0xfffffffe;
 
    private Waiter? _waiter;
    private FakeTimeProvider? _timeProvider;
    private TimerCallback? _callback;
    private object? _state;
 
    public Timer(FakeTimeProvider timeProvider, TimerCallback callback, object? state)
    {
        _timeProvider = timeProvider;
        _callback = callback;
        _state = state;
    }
 
    public bool Change(TimeSpan dueTime, TimeSpan period)
    {
        var dueTimeMs = (long)dueTime.TotalMilliseconds;
        var periodMs = (long)period.TotalMilliseconds;
 
#pragma warning disable S3236 // Caller information arguments should not be provided explicitly
        _ = Throw.IfOutOfRange(dueTimeMs, -1, MaxSupportedTimeout, nameof(dueTime));
        _ = Throw.IfOutOfRange(periodMs, -1, MaxSupportedTimeout, nameof(period));
#pragma warning restore S3236 // Caller information arguments should not be provided explicitly
 
        var timeProvider = _timeProvider;
        if (timeProvider is null)
        {
            // timer has been disposed
            return false;
        }
 
        var waiter = _waiter;
        if (waiter is not null)
        {
            // remove any previous waiter
            timeProvider.RemoveWaiter(waiter);
            _waiter = null;
        }
 
        if (dueTimeMs < 0)
        {
            // this waiter will never wake up, so just bail
            return true;
        }
 
        if (periodMs < 0 || periodMs == Timeout.Infinite)
        {
            // normalize
            period = TimeSpan.Zero;
        }
 
        _waiter = waiter = new Waiter(_callback!, _state, period.Ticks);
        timeProvider.AddWaiter(waiter, dueTime.Ticks);
        return true;
    }
 
    // In case the timer is not disposed, this will remove the Waiter instance from the provider.
    ~Timer() => Dispose(false);
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    public ValueTask DisposeAsync()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
#if NET5_0_OR_GREATER
        return ValueTask.CompletedTask;
#else
        return default;
#endif
    }
 
    private void Dispose(bool _)
    {
        var waiter = _waiter;
        if (waiter is not null)
        {
            _timeProvider?.RemoveWaiter(waiter);
            _waiter = null;
        }
 
        _timeProvider = null;
        _callback = null;
        _state = null;
    }
}