File: system\threading\timer.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//
// <OWNER>Microsoft</OWNER>
 
namespace System.Threading 
{
    using System;
    using System.Security;
    using System.Security.Permissions;
    using Microsoft.Win32;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.Versioning;
    using System.Diagnostics.Contracts;
    using System.Diagnostics.Tracing;
    using Microsoft.Win32.SafeHandles;
 
 
        
    [System.Runtime.InteropServices.ComVisible(true)]
    public delegate void TimerCallback(Object state);
 
    //
    // TimerQueue maintains a list of active timers in this AppDomain.  We use a single native timer, supplied by the VM,
    // to schedule all managed timers in the AppDomain.
    //
    // Perf assumptions:  We assume that timers are created and destroyed frequently, but rarely actually fire.
    // There are roughly two types of timer:
    //
    //  - timeouts for operations.  These are created and destroyed very frequently, but almost never fire, because
    //    the whole point is that the timer only fires if something has gone wrong.
    //
    //  - scheduled background tasks.  These typically do fire, but they usually have quite long durations.
    //    So the impact of spending a few extra cycles to fire these is negligible.
    //
    // Because of this, we want to choose a data structure with very fast insert and delete times, but we can live
    // with linear traversal times when firing timers.
    //
    // The data structure we've chosen is an unordered doubly-linked list of active timers.  This gives O(1) insertion
    // and removal, and O(N) traversal when finding expired timers.
    //
    // Note that all instance methods of this class require that the caller hold a lock on TimerQueue.Instance.
    //
    class TimerQueue
    {
        #region singleton pattern implementation
 
        // The one-and-only TimerQueue for the AppDomain.
        static TimerQueue s_queue = new TimerQueue();
 
        public static TimerQueue Instance
        {
            get { return s_queue; }
        }
 
        private TimerQueue()
        {
            // empty private constructor to ensure we remain a singleton.
        }
 
        #endregion
 
        #region interface to native per-AppDomain timer
 
        //
        // We need to keep our notion of time synchronized with the calls to SleepEx that drive
        // the underlying native timer.  In Win8, SleepEx does not count the time the machine spends
        // sleeping/hibernating.  Environment.TickCount (GetTickCount) *does* count that time,
        // so we will get out of sync with SleepEx if we use that method.
        //
        // So, on Win8, we use QueryUnbiasedInterruptTime instead; this does not count time spent
        // in sleep/hibernate mode.
        //
        private static int TickCount
        {
            [SecuritySafeCritical]
            get
            {
                if (Environment.IsWindows8OrAbove)
                {
                    ulong time100ns;
 
                    bool result = Win32Native.QueryUnbiasedInterruptTime(out time100ns);
                    if (!result)
                        throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
 
                    // convert to 100ns to milliseconds, and truncate to 32 bits.
                    return (int)(uint)(time100ns / 10000);
                }
                else
                {
                    return Environment.TickCount;
                }
            }
        }
 
        //
        // We use a SafeHandle to ensure that the native timer is destroyed when the AppDomain is unloaded.
        //
        [SecurityCritical]
        internal class AppDomainTimerSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public AppDomainTimerSafeHandle()
                : base(true)
            {
            }
 
            [SecurityCritical]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            protected override bool ReleaseHandle()
            {
                return DeleteAppDomainTimer(handle);
            }
        }
 
        [SecurityCritical]
        AppDomainTimerSafeHandle m_appDomainTimer;
 
        bool m_isAppDomainTimerScheduled;
        int m_currentAppDomainTimerStartTicks;
        uint m_currentAppDomainTimerDuration;
 
        [SecuritySafeCritical]
        private bool EnsureAppDomainTimerFiresBy(uint requestedDuration)
        {
            //
            // The VM's timer implementation does not work well for very long-duration timers.
            // See kb 950807.
            // So we'll limit our native timer duration to a "small" value.
            // This may cause us to attempt to fire timers early, but that's ok - 
            // we'll just see that none of our timers has actually reached its due time,
            // and schedule the native timer again.
            //
            const uint maxPossibleDuration = 0x0fffffff;
            uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
 
            if (m_isAppDomainTimerScheduled)
            {
                uint elapsed = (uint)(TickCount - m_currentAppDomainTimerStartTicks);
                if (elapsed >= m_currentAppDomainTimerDuration)
                    return true; //the timer's about to fire
 
                uint remainingDuration = m_currentAppDomainTimerDuration - elapsed;
                if (actualDuration >= remainingDuration)
                    return true; //the timer will fire earlier than this request
            }
 
            // If Pause is underway then do not schedule the timers
            // A later update during resume will re-schedule
            if(m_pauseTicks != 0)
            {
                Contract.Assert(!m_isAppDomainTimerScheduled);
                Contract.Assert(m_appDomainTimer == null);
                return true;
            }
 
            if (m_appDomainTimer == null || m_appDomainTimer.IsInvalid)
            {
                Contract.Assert(!m_isAppDomainTimerScheduled);
 
                m_appDomainTimer = CreateAppDomainTimer(actualDuration, 0);
                if (!m_appDomainTimer.IsInvalid)
                {
                    m_isAppDomainTimerScheduled = true;
                    m_currentAppDomainTimerStartTicks = TickCount;
                    m_currentAppDomainTimerDuration = actualDuration;
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                if (ChangeAppDomainTimer(m_appDomainTimer, actualDuration))
                {
                    m_isAppDomainTimerScheduled = true;
                    m_currentAppDomainTimerStartTicks = TickCount;
                    m_currentAppDomainTimerDuration = actualDuration;
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
 
        //
        // The VM calls this when the native timer fires.
        //
        [SecuritySafeCritical]
        internal static void AppDomainTimerCallback(int id)
        {
            if (Timer.UseNetCoreTimer)
            {
                NetCore.TimerQueue.AppDomainTimerCallback(id);
            }
            else
            {
                Contract.Assert(id == 0);
                Instance.FireNextTimers();
            }
        }
 
        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime, int id);
 
        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern bool ChangeAppDomainTimer(AppDomainTimerSafeHandle handle, uint dueTime);
 
        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        internal static extern bool DeleteAppDomainTimer(IntPtr handle);
 
        #endregion
 
        #region Firing timers
 
        //
        // The list of timers
        //
        TimerQueueTimer m_timers;
 
 
        volatile int m_pauseTicks = 0; // Time when Pause was called
 
        [SecurityCritical]
        internal void Pause()
        {
            lock(this)
            {
                // Delete the native timer so that no timers are fired in the Pause zone
                if(m_appDomainTimer != null && !m_appDomainTimer.IsInvalid)
                {
                    m_appDomainTimer.Dispose();
                    m_appDomainTimer = null;
                    m_isAppDomainTimerScheduled = false;
                    m_pauseTicks = TickCount;
                }
            }
        }
 
        [SecurityCritical]
        internal void Resume()
        {
            //
            // Update timers to adjust their due-time to accomodate Pause/Resume
            //
            lock (this)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    int pauseTicks = m_pauseTicks;
                    m_pauseTicks = 0; // Set this to 0 so that now timers can be scheduled
 
                    int resumedTicks = TickCount;
                    int pauseDuration = resumedTicks - pauseTicks;
 
                    bool haveTimerToSchedule = false;
                    uint nextAppDomainTimerDuration = uint.MaxValue;      
            
                    TimerQueueTimer timer = m_timers;
                    while (timer != null)
                    {
                        Contract.Assert(timer.m_dueTime != Timeout.UnsignedInfinite);
                        Contract.Assert(resumedTicks >= timer.m_startTicks);
 
                        uint elapsed; // How much of the timer dueTime has already elapsed
 
                        // Timers started before the paused event has to be sufficiently delayed to accomodate 
                        // for the Pause time. However, timers started after the Paused event shouldnt be adjusted. 
                        // E.g. ones created by the app in its Activated event should fire when it was designated.
                        // The Resumed event which is where this routine is executing is after this Activated and hence 
                        // shouldn't delay this timer
 
                        if(timer.m_startTicks <= pauseTicks)
                            elapsed = (uint)(pauseTicks - timer.m_startTicks);
                        else
                            elapsed = (uint)(resumedTicks - timer.m_startTicks);
 
                        // Handling the corner cases where a Timer was already due by the time Resume is happening,
                        // We shouldn't delay those timers. 
                        // Example is a timer started in App's Activated event with a very small duration
                        timer.m_dueTime = (timer.m_dueTime > elapsed) ? timer.m_dueTime - elapsed : 0;;
                        timer.m_startTicks = resumedTicks; // re-baseline
 
                        if (timer.m_dueTime < nextAppDomainTimerDuration)
                        {
                            haveTimerToSchedule = true;
                            nextAppDomainTimerDuration = timer.m_dueTime;
                        }
 
                        timer = timer.m_next;
                    }
                    
                    if (haveTimerToSchedule)
                    {
                        EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration);
                    }
                }
            }
        }
 
 
        //
        // Fire any timers that have expired, and update the native timer to schedule the rest of them.
        //
        private void FireNextTimers()
        {
            //
            // we fire the first timer on this thread; any other timers that might have fired are queued
            // to the ThreadPool.
            //
            TimerQueueTimer timerToFireOnThisThread = null;
 
            lock (this)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    //
                    // since we got here, that means our previous timer has fired.
                    //
                    m_isAppDomainTimerScheduled = false;
                    bool haveTimerToSchedule = false;
                    uint nextAppDomainTimerDuration = uint.MaxValue;
 
                    int nowTicks = TickCount;
 
                    //
                    // Sweep through all timers.  The ones that have reached their due time
                    // will fire.  We will calculate the next native timer due time from the
                    // other timers.
                    //
                    TimerQueueTimer timer = m_timers;
                    while (timer != null)
                    {
                        Contract.Assert(timer.m_dueTime != Timeout.UnsignedInfinite);
 
                        uint elapsed = (uint)(nowTicks - timer.m_startTicks);
                        if (elapsed >= timer.m_dueTime)
                        {
                            //
                            // Remember the next timer in case we delete this one
                            //
                            TimerQueueTimer nextTimer = timer.m_next;
 
                            if (timer.m_period != Timeout.UnsignedInfinite)
                            {
                                timer.m_startTicks = nowTicks;
                                timer.m_dueTime = timer.m_period;
 
                                //
                                // This is a repeating timer; schedule it to run again.
                                //
                                if (timer.m_dueTime < nextAppDomainTimerDuration)
                                {
                                    haveTimerToSchedule = true;
                                    nextAppDomainTimerDuration = timer.m_dueTime;
                                }
                            }
                            else
                            {
                                //
                                // Not repeating; remove it from the queue
                                //
                                DeleteTimer(timer);
                            }
 
                            //
                            // If this is the first timer, we'll fire it on this thread.  Otherwise, queue it
                            // to the ThreadPool.
                            //
                            if (timerToFireOnThisThread == null)
                                timerToFireOnThisThread = timer;
                            else
                                QueueTimerCompletion(timer);
 
                            timer = nextTimer;
                        }
                        else
                        {
                            //
                            // This timer hasn't fired yet.  Just update the next time the native timer fires.
                            //
                            uint remaining = timer.m_dueTime - elapsed;
                            if (remaining < nextAppDomainTimerDuration)
                            {
                                haveTimerToSchedule = true;
                                nextAppDomainTimerDuration = remaining;
                            }
                            timer = timer.m_next;
                        }
                    }
 
                    if (haveTimerToSchedule)
                        EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration);
                }
            }
 
            //
            // Fire the user timer outside of the lock!
            //
            if (timerToFireOnThisThread != null)
                timerToFireOnThisThread.Fire();
        }
 
        [SecuritySafeCritical]
        private static void QueueTimerCompletion(TimerQueueTimer timer)
        {
            WaitCallback callback = s_fireQueuedTimerCompletion;
            if (callback == null)
                s_fireQueuedTimerCompletion = callback = new WaitCallback(FireQueuedTimerCompletion);
 
            // Can use "unsafe" variant because we take care of capturing and restoring
            // the ExecutionContext.
            ThreadPool.UnsafeQueueUserWorkItem(callback, timer);
        }
 
        private static WaitCallback s_fireQueuedTimerCompletion;
 
        private static void FireQueuedTimerCompletion(object state)
        {
            ((TimerQueueTimer)state).Fire();
        }
 
        #endregion
 
        #region Queue implementation
 
        public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
        {
            if (timer.m_dueTime == Timeout.UnsignedInfinite)
            {
                // the timer is not in the list; add it (as the head of the list).
                timer.m_next = m_timers;
                timer.m_prev = null;
                if (timer.m_next != null)
                    timer.m_next.m_prev = timer;
                m_timers = timer;
            }
            timer.m_dueTime = dueTime;
            timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period;
            timer.m_startTicks = TickCount;
            return EnsureAppDomainTimerFiresBy(dueTime);
        }
 
        public void DeleteTimer(TimerQueueTimer timer)
        {
            if (timer.m_dueTime != Timeout.UnsignedInfinite)
            {
                if (timer.m_next != null)
                    timer.m_next.m_prev = timer.m_prev;
                if (timer.m_prev != null)
                    timer.m_prev.m_next = timer.m_next;
                if (m_timers == timer)
                    m_timers = timer.m_next;
 
                timer.m_dueTime = Timeout.UnsignedInfinite;
                timer.m_period = Timeout.UnsignedInfinite;
                timer.m_startTicks = 0;
                timer.m_prev = null;
                timer.m_next = null;
            }
        }
 
        #endregion
    }
 
    //
    // A timer in our TimerQueue.
    //
    sealed class TimerQueueTimer
    {
        //
        // All fields of this class are protected by a lock on TimerQueue.Instance.
        //
        // The first four fields are maintained by TimerQueue itself.
        //
        internal TimerQueueTimer m_next;
        internal TimerQueueTimer m_prev;
 
        //
        // The time, according to TimerQueue.TickCount, when this timer's current interval started.
        //
        internal int m_startTicks;
 
        //
        // Timeout.UnsignedInfinite if we are not going to fire.  Otherwise, the offset from m_startTime when we will fire.
        //
        internal uint m_dueTime;
 
        //
        // Timeout.UnsignedInfinite if we are a single-shot timer.  Otherwise, the repeat interval.
        //
        internal uint m_period;
 
        //
        // Info about the user's callback
        //
        readonly TimerCallback m_timerCallback;
        readonly Object m_state;
        readonly ExecutionContext m_executionContext;
 
 
        //
        // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
        // after all pending callbacks are complete.  We set m_canceled to prevent any callbacks that
        // are already queued from running.  We track the number of callbacks currently executing in 
        // m_callbacksRunning.  We set m_notifyWhenNoCallbacksRunning only when m_callbacksRunning
        // reaches zero.
        //
        int m_callbacksRunning;
        volatile bool m_canceled;
        volatile WaitHandle m_notifyWhenNoCallbacksRunning;
 
 
        [SecurityCritical]
        internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, ref StackCrawlMark stackMark)
        {
            m_timerCallback = timerCallback;
            m_state = state;
            m_dueTime = Timeout.UnsignedInfinite;
            m_period = Timeout.UnsignedInfinite;
 
            if (!ExecutionContext.IsFlowSuppressed())
            {
                m_executionContext = ExecutionContext.Capture(
                    ref stackMark,
                    ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase);
            }
 
            //
            // After the following statement, the timer may fire.  No more manipulation of timer state outside of
            // the lock is permitted beyond this point!
            //
            if (dueTime != Timeout.UnsignedInfinite)
                Change(dueTime, period);
        }
 
 
        internal bool Change(uint dueTime, uint period)
        {
            bool success;
 
            lock (TimerQueue.Instance)
            {
                if (m_canceled)
                    throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_Generic"));
 
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    m_period = period;
 
                    if (dueTime == Timeout.UnsignedInfinite)
                    {
                        TimerQueue.Instance.DeleteTimer(this);
                        success = true;
                    }
                    else
                    {
                        if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
                            FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true);
 
                        success = TimerQueue.Instance.UpdateTimer(this, dueTime, period);
                    }
                }
            }
 
            return success;
        }
 
 
        public void Close()
        {
            lock (TimerQueue.Instance)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    if (!m_canceled)
                    {
                        m_canceled = true;
                        TimerQueue.Instance.DeleteTimer(this);
                    }
                }
            }
        }
 
 
        public bool Close(WaitHandle toSignal)
        {
            bool success;
            bool shouldSignal = false;
 
            lock (TimerQueue.Instance)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    if (m_canceled)
                    {
                        success = false;
                    }
                    else
                    {
                        m_canceled = true;
                        m_notifyWhenNoCallbacksRunning = toSignal;
                        TimerQueue.Instance.DeleteTimer(this);
 
                        if (m_callbacksRunning == 0)
                            shouldSignal = true;
 
                        success = true;
                    }
                }
            }
 
            if (shouldSignal)
                SignalNoCallbacksRunning();
 
            return success;
        }
 
 
        internal void Fire()
        {
            bool canceled = false;
 
            lock (TimerQueue.Instance)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    canceled = m_canceled;
                    if (!canceled)
                        m_callbacksRunning++;
                }
            }
 
            if (canceled)
                return;
 
            CallCallback();
 
            bool shouldSignal = false;
            lock (TimerQueue.Instance)
            {
                // prevent ThreadAbort while updating state
                try { }
                finally
                {
                    m_callbacksRunning--;
                    if (m_canceled && m_callbacksRunning == 0 && m_notifyWhenNoCallbacksRunning != null)
                        shouldSignal = true;
                }
            }
 
            if (shouldSignal)
                SignalNoCallbacksRunning();
        }
 
        [SecuritySafeCritical]
        internal void SignalNoCallbacksRunning()
        {
            Win32Native.SetEvent(m_notifyWhenNoCallbacksRunning.SafeWaitHandle);
        }
 
        [SecuritySafeCritical]
        internal void CallCallback()
        {
            if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
                FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
 
            // call directly if EC flow is suppressed
            if (m_executionContext == null)
            {
                m_timerCallback(m_state);
            }
            else
            {
                using (ExecutionContext executionContext = 
                    m_executionContext.IsPreAllocatedDefault ? m_executionContext : m_executionContext.CreateCopy())
                {
                    ContextCallback callback = s_callCallbackInContext;
                    if (callback == null)
                        s_callCallbackInContext = callback = new ContextCallback(CallCallbackInContext);
                    
                    ExecutionContext.Run(
                        executionContext,
                        callback,
                        this,  // state
                        true); // ignoreSyncCtx
                }
            }
        }
 
        [SecurityCritical]
        private static ContextCallback s_callCallbackInContext;
 
        [SecurityCritical]
        private static void CallCallbackInContext(object state)
        {
            TimerQueueTimer t = (TimerQueueTimer)state;
            t.m_timerCallback(t.m_state);
        }
    }
 
    //
    // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer 
    // if the Timer is collected.
    // This is necessary because Timer itself cannot use its finalizer for this purpose.  If it did,
    // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
    // You might ask, wouldn't that be a good thing?  Maybe (though it would be even better to offer this
    // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
    // change, because any code that happened to be suppressing finalization of Timer objects would now
    // unwittingly be changing the lifetime of those timers.
    //
    sealed class TimerHolder
    {
        private object m_timer;
        
        public TimerHolder(object timer)
        {
            Contract.Assert(timer is TimerQueueTimer || timer is NetCore.TimerQueueTimer);
            Contract.Assert((timer is NetCore.TimerQueueTimer) == Timer.UseNetCoreTimer);
 
            m_timer = timer;
        }
 
        ~TimerHolder() 
        { 
            //
            // If shutdown has started, another thread may be suspended while holding the timer lock.
            // So we can't safely close the timer.  
            //
            // Similarly, we should not close the timer during AD-unload's live-object finalization phase.
            // A rude abort may have prevented us from releasing the lock.
            //
            // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be
            // allowed to run in this AppDomain.
            //
            if (Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload())
                return;
 
            if (Timer.UseNetCoreTimer)
            {
                NetCoreTimer.Close();
            }
            else
            {
                NetFxTimer.Close();
            }
        }
 
        private TimerQueueTimer NetFxTimer
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                Contract.Assert(!Timer.UseNetCoreTimer);
                Contract.Assert(m_timer is TimerQueueTimer);
 
                return (TimerQueueTimer)m_timer;
            }
        }
 
        private NetCore.TimerQueueTimer NetCoreTimer
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                Contract.Assert(Timer.UseNetCoreTimer);
                Contract.Assert(m_timer is NetCore.TimerQueueTimer);
 
                return (NetCore.TimerQueueTimer)m_timer;
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Change(uint dueTime, uint period) =>
            Timer.UseNetCoreTimer ? NetCoreTimer.Change(dueTime, period) : NetFxTimer.Change(dueTime, period);
 
        public void Close()
        {
            if (Timer.UseNetCoreTimer)
            {
                NetCoreTimer.Close();
            }
            else
            {
                NetFxTimer.Close();
            }
 
            GC.SuppressFinalize(this);
        }
 
        public bool Close(WaitHandle notifyObject)
        {
            bool result = Timer.UseNetCoreTimer ? NetCoreTimer.Close(notifyObject) : NetFxTimer.Close(notifyObject);
            GC.SuppressFinalize(this);
            return result;
        }
    }
 
 
    [HostProtection(Synchronization=true, ExternalThreading=true)]
    [System.Runtime.InteropServices.ComVisible(true)]
#if FEATURE_REMOTING
    public sealed class Timer : MarshalByRefObject, IDisposable
#else // FEATURE_REMOTING
    public sealed class Timer : IDisposable
#endif // FEATURE_REMOTING
    {
        internal static readonly bool UseNetCoreTimer = AppContextSwitches.UseNetCoreTimer;
 
        private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
 
        private TimerHolder m_timer;
 
        [SecuritySafeCritical]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable
        public Timer(TimerCallback callback, 
                     Object        state,  
                     int           dueTime,
                     int           period)
        {
            if (dueTime < -1)
                throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (period < -1 )
                throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            Contract.EndContractBlock();
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
 
            TimerSetup(callback,state,(UInt32)dueTime,(UInt32)period,ref stackMark);
        }
 
        [SecuritySafeCritical]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable
        public Timer(TimerCallback callback, 
                     Object        state,  
                     TimeSpan      dueTime,
                     TimeSpan      period)
        {                
            long dueTm = (long)dueTime.TotalMilliseconds;
            if (dueTm < -1)
                throw new ArgumentOutOfRangeException("dueTm",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (dueTm > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("dueTm",Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge"));
 
            long periodTm = (long)period.TotalMilliseconds;
            if (periodTm < -1)
                throw new ArgumentOutOfRangeException("periodTm",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (periodTm > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("periodTm",Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge"));
 
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            TimerSetup(callback,state,(UInt32)dueTm,(UInt32)periodTm,ref stackMark);
        }
 
        [CLSCompliant(false)]
        [SecuritySafeCritical]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable
        public Timer(TimerCallback callback, 
                     Object        state,  
                     UInt32        dueTime,
                     UInt32        period)
        {
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            TimerSetup(callback,state,dueTime,period,ref stackMark);
        }
 
        [SecuritySafeCritical]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable                                        
        public Timer(TimerCallback callback, 
                     Object        state,  
                     long          dueTime,
                     long          period)
        {
            if (dueTime < -1)
                throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (period < -1)
                throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (dueTime > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge"));
            if (period > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge"));
            Contract.EndContractBlock();
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            TimerSetup(callback,state,(UInt32) dueTime, (UInt32) period,ref stackMark);
        }
 
        [SecuritySafeCritical]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable
        public Timer(TimerCallback callback)
        {
            int dueTime = -1;    // we want timer to be registered, but not activated.  Requires caller to call
            int period = -1;    // Change after a timer instance is created.  This is to avoid the potential
                                // for a timer to be fired before the returned value is assigned to the variable,
                                // potentially causing the callback to reference a bogus value (if passing the timer to the callback). 
            
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period, ref stackMark);
        }
 
        [SecurityCritical]
        private void TimerSetup(TimerCallback callback,
                                Object state, 
                                UInt32 dueTime,
                                UInt32 period,
                                ref StackCrawlMark stackMark)
        {
            if (callback == null)
                throw new ArgumentNullException("TimerCallback");
            Contract.EndContractBlock();
 
            object timer;
            if (UseNetCoreTimer)
            {
                // As of https://github.com/dotnet/coreclr/pull/18670, NetCore allows the caller to specify the value of
                // 'flowExecutionContext' and that change has not been ported to NetFx. The old behavior is preserved here.
                timer = new NetCore.TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext: true, stackMark: ref stackMark);
            }
            else
            {
                timer = new TimerQueueTimer(callback, state, dueTime, period, ref stackMark);
            }
            m_timer = new TimerHolder(timer);
        }
 
        [SecurityCritical]
        internal static void Pause()
        {
            if (UseNetCoreTimer)
            {
                NetCore.TimerQueue.PauseAll();
            }
            else
            {
                TimerQueue.Instance.Pause();
            }
        }
 
        [SecurityCritical]
        internal static void Resume()
        {
            if (UseNetCoreTimer)
            {
                NetCore.TimerQueue.ResumeAll();
            }
            else
            {
                TimerQueue.Instance.Resume();
            }
        }
     
        public bool Change(int dueTime, int period)
        {
            if (dueTime < -1 )
                throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (period < -1)
                throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            Contract.EndContractBlock();
 
            return m_timer.Change((UInt32)dueTime, (UInt32)period);
        }
 
        public bool Change(TimeSpan dueTime, TimeSpan period)
        {
            return Change((long) dueTime.TotalMilliseconds, (long) period.TotalMilliseconds);
        }
 
        [CLSCompliant(false)]
        public bool Change(UInt32 dueTime, UInt32 period)
        {
            return m_timer.Change(dueTime, period);
        }
 
        public bool Change(long dueTime, long period)
        {
            if (dueTime < -1 )
                throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (period < -1)
                throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
            if (dueTime > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge"));
            if (period > MAX_SUPPORTED_TIMEOUT)
                throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge"));
            Contract.EndContractBlock();
 
            return m_timer.Change((UInt32)dueTime, (UInt32)period);
        }
    
        public bool Dispose(WaitHandle notifyObject)
        {
            if (notifyObject==null)
                throw new ArgumentNullException("notifyObject");
            Contract.EndContractBlock();
 
            return m_timer.Close(notifyObject);
        }
         
        public void Dispose()
        {
            m_timer.Close();
        }
 
        internal void KeepRootedWhileScheduled()
        {
            GC.SuppressFinalize(m_timer);
        }
    }
}