// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Reactive.Concurrency; using System.Threading; namespace System.Reactive.PlatformServices { /// /// (Infrastructure) Provides access to local system clock services. /// /// /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. /// No guarantees are made about forward compatibility of the type's functionality and its usage. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class SystemClock { private static Lazy s_serviceSystemClock = new Lazy(InitializeSystemClock); private static Lazy s_serviceSystemClockChanged = new Lazy(InitializeSystemClockChanged); #if NO_WEAKREFOFT private static readonly HashSet s_systemClockChanged = new HashSet(); #else private static readonly HashSet> s_systemClockChanged = new HashSet>(); #endif private static IDisposable s_systemClockChangedHandlerCollector; private static int _refCount; /// /// Gets the local system clock time. /// public static DateTimeOffset UtcNow { get { return s_serviceSystemClock.Value.UtcNow; } } /// /// Adds a reference to the system clock monitor, causing it to be sending notifications. /// /// Thrown when the system doesn't support sending clock change notifications. public static void AddRef() { if (Interlocked.Increment(ref _refCount) == 1) { s_serviceSystemClockChanged.Value.SystemClockChanged += OnSystemClockChanged; } } /// /// Removes a reference to the system clock monitor, causing it to stop sending notifications /// if the removed reference was the last one. /// public static void Release() { if (Interlocked.Decrement(ref _refCount) == 0) { s_serviceSystemClockChanged.Value.SystemClockChanged -= OnSystemClockChanged; } } private static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e) { lock (s_systemClockChanged) { foreach (var entry in s_systemClockChanged) { #if NO_WEAKREFOFT var scheduler = entry.Target as LocalScheduler; if (scheduler != null) #else var scheduler = default(LocalScheduler); if (entry.TryGetTarget(out scheduler)) #endif { scheduler.SystemClockChanged(sender, e); } } } } private static ISystemClock InitializeSystemClock() { return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultSystemClock(); } private static INotifySystemClockChanged InitializeSystemClockChanged() { return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultSystemClockMonitor(); } internal static void Register(LocalScheduler scheduler) { // // LocalScheduler maintains per-instance work queues that need revisiting // upon system clock changes. We need to be careful to avoid keeping those // scheduler instances alive by the system clock monitor, so we use weak // references here. In particular, AsyncLockScheduler in ImmediateScheduler // can have a lot of instances, so we need to collect spurious handlers // at regular times. // lock (s_systemClockChanged) { #if NO_WEAKREFOFT s_systemClockChanged.Add(new WeakReference(scheduler, false)); #else s_systemClockChanged.Add(new WeakReference(scheduler)); #endif if (s_systemClockChanged.Count == 1) { s_systemClockChangedHandlerCollector = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(CollectHandlers, TimeSpan.FromSeconds(30)); } else if (s_systemClockChanged.Count % 64 == 0) { CollectHandlers(); } } } private static void CollectHandlers() { // // The handler collector merely collects the WeakReference instances // that are kept in the hash set. The underlying scheduler itself will // be collected due to the weak reference. Unfortunately, we can't use // the ConditionalWeakTable type here because we need to // be able to enumerate the keys. // lock (s_systemClockChanged) { #if NO_WEAKREFOFT var remove = default(HashSet); #else var remove = default(HashSet>); #endif foreach (var handler in s_systemClockChanged) { #if NO_WEAKREFOFT var scheduler = handler.Target as LocalScheduler; if (scheduler == null) #else var scheduler = default(LocalScheduler); if (!handler.TryGetTarget(out scheduler)) #endif { if (remove == null) { #if NO_WEAKREFOFT remove = new HashSet(); #else remove = new HashSet>(); #endif } remove.Add(handler); } } if (remove != null) { foreach (var handler in remove) { s_systemClockChanged.Remove(handler); } } if (s_systemClockChanged.Count == 0) { s_systemClockChangedHandlerCollector.Dispose(); s_systemClockChangedHandlerCollector = null; } } } } /// /// (Infrastructure) Provides access to the local system clock. /// /// /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. /// No guarantees are made about forward compatibility of the type's functionality and its usage. /// [EditorBrowsable(EditorBrowsableState.Never)] public interface ISystemClock { /// /// Gets the current time. /// DateTimeOffset UtcNow { get; } } /// /// (Infrastructure) Provides a mechanism to notify local schedulers about system clock changes. /// /// /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. /// No guarantees are made about forward compatibility of the type's functionality and its usage. /// [EditorBrowsable(EditorBrowsableState.Never)] public interface INotifySystemClockChanged { /// /// Event that gets raised when a system clock change is detected. /// event EventHandler SystemClockChanged; } /// /// (Infrastructure) Event arguments for system clock change notifications. /// /// /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. /// No guarantees are made about forward compatibility of the type's functionality and its usage. /// [EditorBrowsable(EditorBrowsableState.Never)] public class SystemClockChangedEventArgs : EventArgs { /// /// Creates a new system clock notification object with unknown old and new times. /// public SystemClockChangedEventArgs() : this(DateTimeOffset.MinValue, DateTimeOffset.MaxValue) { } /// /// Creates a new system clock notification object with the specified old and new times. /// /// Time before the system clock changed, or DateTimeOffset.MinValue if not known. /// Time after the system clock changed, or DateTimeOffset.MaxValue if not known. public SystemClockChangedEventArgs(DateTimeOffset oldTime, DateTimeOffset newTime) { OldTime = oldTime; NewTime = newTime; } /// /// Gets the time before the system clock changed, or DateTimeOffset.MinValue if not known. /// public DateTimeOffset OldTime { get; private set; } /// /// Gets the time after the system clock changed, or DateTimeOffset.MaxValue if not known. /// public DateTimeOffset NewTime { get; private set; } } #if NO_WEAKREFOFT class WeakReference where T : class { private readonly WeakReference _weakReference; public WeakReference(T value) { _weakReference = new WeakReference(value); } public bool TryGetTarget(out T value) { value = (T)_weakReference.Target; return value != null; } } #endif #if NO_HASHSET class HashSet : IEnumerable { private readonly Dictionary _dictionary = new Dictionary(); public int Count { get { return _dictionary.Count; } } public IEnumerator GetEnumerator() { return _dictionary.Keys.GetEnumerator(); } public void Add(T value) { _dictionary.Add(value, null); } public void Remove(T value) { _dictionary.Remove(value); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } #endif }