// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. using System.ComponentModel; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; namespace System.Reactive.PlatformServices { /// /// (Infrastructure) Provides access to the local system clock. /// [EditorBrowsable(EditorBrowsableState.Never)] public class DefaultSystemClock : ISystemClock { /// /// Gets the current time. /// public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; } internal class DefaultSystemClockMonitor : PeriodicTimerSystemClockMonitor { private static readonly TimeSpan DefaultPeriod = TimeSpan.FromSeconds(1); public DefaultSystemClockMonitor() : base(DefaultPeriod) { } } /// /// (Infrastructure) Monitors for system clock changes based on a periodic timer. /// [EditorBrowsable(EditorBrowsableState.Never)] public class PeriodicTimerSystemClockMonitor : INotifySystemClockChanged { private readonly TimeSpan _period; private SerialDisposableValue _timer; /// /// Use the Unix milliseconds for the current time /// so it can be atomically read/written without locking. /// private long _lastTimeUnixMillis; private EventHandler? _systemClockChanged; private const int SyncMaxRetries = 100; private const double SyncMaxDelta = 10; private const int MaxError = 100; /// /// Creates a new monitor for system clock changes with the specified polling frequency. /// /// Polling frequency for system clock changes. public PeriodicTimerSystemClockMonitor(TimeSpan period) { _period = period; } /// /// Event that gets raised when a system clock change is detected. /// public event EventHandler SystemClockChanged { add { NewTimer(); _systemClockChanged += value; } remove { _systemClockChanged -= value; _timer.Disposable = Disposable.Empty; } } private void NewTimer() { _timer.Disposable = Disposable.Empty; var n = 0L; for (; ; ) { var now = SystemClock.UtcNow.ToUnixTimeMilliseconds(); Interlocked.Exchange(ref _lastTimeUnixMillis, now); _timer.Disposable = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(TimeChanged, _period); if (Math.Abs(SystemClock.UtcNow.ToUnixTimeMilliseconds() - now) <= SyncMaxDelta) { break; } if (_timer.Disposable == Disposable.Empty) { break; } if (++n >= SyncMaxRetries) { Task.Delay((int)SyncMaxDelta).Wait(); } } } private void TimeChanged() { var newTime = SystemClock.UtcNow; var now = newTime.ToUnixTimeMilliseconds(); var last = Volatile.Read(ref _lastTimeUnixMillis); var oldTime = (long)(last + _period.TotalMilliseconds); var diff = now - oldTime; if (Math.Abs(diff) >= MaxError) { _systemClockChanged?.Invoke(this, new SystemClockChangedEventArgs( DateTimeOffset.FromUnixTimeMilliseconds(oldTime), newTime)); NewTimer(); } else { Interlocked.Exchange(ref _lastTimeUnixMillis, SystemClock.UtcNow.ToUnixTimeMilliseconds()); } } } }