diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs index 7f0aff65eb594a0b3812baa6bfd9d56bde5637c0..c9469c0a05d30d77159d6e6f294a7e998fb123af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs @@ -2,104 +2,141 @@ // 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.Collections.Generic; using System.Diagnostics; namespace System.Threading { - internal partial class TimerQueue + // + // Unix-specific implementation of Timer + // + internal partial class TimerQueue : IThreadPoolWorkItem { + private static List s_scheduledTimers; + private static List s_scheduledTimersToFire; + /// /// This event is used by the timer thread to wait for timer expiration. It is also /// used to notify the timer thread that a new timer has been set. /// - private AutoResetEvent _timerEvent; + private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false); - /// - /// This field stores the value of next timer that the timer thread should install. - /// - private volatile int _nextTimerDuration; + private bool _isScheduled; + private int _scheduledDueTimeMs; private TimerQueue(int id) { } - private bool SetTimer(uint actualDuration) + private static List InitializeScheduledTimerManager_Locked() { - // Note: AutoResetEvent.WaitOne takes an Int32 value as a timeout. - // The TimerQueue code ensures that timer duration is not greater than max Int32 value - Debug.Assert(actualDuration <= (uint)int.MaxValue); - _nextTimerDuration = (int)actualDuration; - - // If this is the first time the timer is set then we need to create a thread that - // will manage and respond to timer requests. Otherwise, simply signal the timer thread - // to notify it that the timer duration has changed. - if (_timerEvent == null) + Debug.Assert(s_scheduledTimers == null); + + var timers = new List(Instances.Length); + if (s_scheduledTimersToFire == null) { - _timerEvent = new AutoResetEvent(false); - Thread thread = new Thread(TimerThread); - thread.IsBackground = true; // Keep this thread from blocking process shutdown - thread.Start(); + s_scheduledTimersToFire = new List(Instances.Length); } - else + + Thread timerThread = new Thread(TimerThread); + timerThread.IsBackground = true; + timerThread.Start(); + + // Do this after creating the thread in case thread creation fails so that it will try again next time + s_scheduledTimers = timers; + return timers; + } + + private bool SetTimer(uint actualDuration) + { + Debug.Assert((int)actualDuration >= 0); + int dueTimeMs = TickCount + (int)actualDuration; + AutoResetEvent timerEvent = s_timerEvent; + lock (timerEvent) { - _timerEvent.Set(); + if (!_isScheduled) + { + List timers = s_scheduledTimers; + if (timers == null) + { + timers = InitializeScheduledTimerManager_Locked(); + } + + timers.Add(this); + _isScheduled = true; + } + + _scheduledDueTimeMs = dueTimeMs; } + timerEvent.Set(); return true; } - /// /// This method is executed on a dedicated a timer thread. Its purpose is - /// to handle timer request and notify the TimerQueue when a timer expires. + /// to handle timer requests and notify the TimerQueue when a timer expires. /// - private void TimerThread() + private static void TimerThread() { - // Get wait time for the next timer - int currentTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite); + AutoResetEvent timerEvent = s_timerEvent; + List timersToFire = s_scheduledTimersToFire; + List timers; + lock (timerEvent) + { + timers = s_scheduledTimers; + } + int shortestWaitDurationMs = Timeout.Infinite; while (true) { - // Wait for the current timer to expire. - // We will be woken up because either 1) the wait times out, which will indicate that - // the current timer has expired and/or 2) the TimerQueue installs a new (earlier) timer. - int startWait = TickCount; - bool timerHasExpired = !_timerEvent.WaitOne(currentTimerInterval); - uint elapsedTime = (uint)(TickCount - startWait); - - // The timer event can be set after this thread reads the new timer interval but before it enters - // the wait state. This can cause a spurious wake up. In addition, expiration of current timer can - // happen almost at the same time as this thread is signaled to install a new timer. To handle - // these cases, we need to update the current interval based on the elapsed time. - if (currentTimerInterval != Timeout.Infinite) + timerEvent.WaitOne(shortestWaitDurationMs); + + int currentTimeMs = TickCount; + shortestWaitDurationMs = int.MaxValue; + lock (timerEvent) { - if (elapsedTime >= currentTimerInterval) - { - timerHasExpired = true; - } - else + for (int i = timers.Count - 1; i >= 0; --i) { - currentTimerInterval -= (int)elapsedTime; + TimerQueue timer = timers[i]; + int waitDurationMs = timer._scheduledDueTimeMs - currentTimeMs; + if (waitDurationMs <= 0) + { + timer._isScheduled = false; + timersToFire.Add(timer); + + int lastIndex = timers.Count - 1; + if (i != lastIndex) + { + timers[i] = timers[lastIndex]; + } + timers.RemoveAt(lastIndex); + continue; + } + + if (waitDurationMs < shortestWaitDurationMs) + { + shortestWaitDurationMs = waitDurationMs; + } } } - // Check whether TimerQueue needs to process expired timers. - if (timerHasExpired) + if (timersToFire.Count > 0) { - FireNextTimers(); - - // When FireNextTimers() installs a new timer, it also sets the timer event. - // Reset the event so the timer thread is not woken up right away unnecessary. - _timerEvent.Reset(); - currentTimerInterval = Timeout.Infinite; + foreach (TimerQueue timerToFire in timersToFire) + { + ThreadPool.UnsafeQueueUserWorkItemInternal(timerToFire, preferLocal: false); + } + timersToFire.Clear(); } - int nextTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite); - if (nextTimerInterval != Timeout.Infinite) + if (shortestWaitDurationMs == int.MaxValue) { - currentTimerInterval = nextTimerInterval; + shortestWaitDurationMs = Timeout.Infinite; } } } + + void IThreadPoolWorkItem.Execute() => FireNextTimers(); } }