// 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());
}
}
}
}