From b0f6cdddae5086fcaea53228905cf586249e0ace Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sun, 27 Jan 2019 20:12:30 +0100 Subject: [PATCH 1/4] Move Timer to shared CoreLib partition. (dotnet/coreclr#22231) * Move Timer to shared CoreLib partition. * Move SignalNoCallbacksRunning back to shared code, add static EventWaitHandle.Set. * Remove AppDomain references from shared Timer code, reshuffle some code out of SetTimer in non-shared code. * Change m_ prefix to match code style. Signed-off-by: dotnet-bot --- .../System.Private.CoreLib.Shared.projitems | 1 + .../Threading/EventWaitHandle.Windows.cs | 5 + .../shared/System/Threading/Timer.cs | 862 ++++++++++++++++++ 3 files changed, 868 insertions(+) create mode 100644 src/System.Private.CoreLib/shared/System/Threading/Timer.cs diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index 16ebd62c5ca..bfd79aa3919 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -809,6 +809,7 @@ + diff --git a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs index 2da53b2b80b..0d41e2a88d9 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs @@ -89,5 +89,10 @@ public bool Set() throw Win32Marshal.GetExceptionForLastWin32Error(); return res; } + + internal static bool Set(SafeWaitHandle waitHandle) + { + return Interop.Kernel32.SetEvent(waitHandle); + } } } diff --git a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs new file mode 100644 index 00000000000..fa4f56b5a3c --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs @@ -0,0 +1,862 @@ +// 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 Internal.Runtime.Augments; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Threading.Tasks; + +using Thread = Internal.Runtime.Augments.RuntimeThread; + +namespace System.Threading +{ + public delegate void TimerCallback(object state); + + // TimerQueue maintains a list of active timers. We use a single native timer to schedule all managed timers + // in the process. + // + // 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, and we can live + // with linear traversal times when firing timers. However, we still want to minimize the number of timers + // we need to traverse while doing the linear walk: in cases where we have lots of long-lived timers as well as + // lots of short-lived timers, when the short-lived timers fire, they incur the cost of walking the long-lived ones. + // + // 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. We maintain two such lists: one for all of the + // timers that'll next fire within a certain threshold, and one for the rest. + // + // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance. + // We partition the timers across multiple TimerQueues, each with its own lock and set of short/long lists, + // in order to minimize contention when lots of threads are concurrently creating and destroying timers often. + internal partial class TimerQueue + { + #region Shared TimerQueue instances + + public static TimerQueue[] Instances { get; } = CreateTimerQueues(); + + private static TimerQueue[] CreateTimerQueues() + { + var queues = new TimerQueue[Environment.ProcessorCount]; + for (int i = 0; i < queues.Length; i++) + { + queues[i] = new TimerQueue(i); + } + return queues; + } + + #endregion + + #region interface to native timer + + private bool _isTimerScheduled; + private int _currentTimerStartTicks; + private uint _currentTimerDuration; + + private bool EnsureTimerFiresBy(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 (_isTimerScheduled) + { + uint elapsed = (uint)(TickCount - _currentTimerStartTicks); + if (elapsed >= _currentTimerDuration) + return true; //the timer's about to fire + + uint remainingDuration = _currentTimerDuration - 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 (_pauseTicks != 0) + { + Debug.Assert(!_isTimerScheduled); + return true; + } + + if (SetTimer(actualDuration)) + { + _isTimerScheduled = true; + _currentTimerStartTicks = TickCount; + _currentTimerDuration = actualDuration; + return true; + } + + return false; + } + + #endregion + + #region Firing timers + + // The two lists of timers that are part of this TimerQueue. They conform to a single guarantee: + // no timer in _longTimers has an absolute next firing time <= _currentAbsoluteThreshold. + // That way, when FireNextTimers is invoked, we always process the short list, and we then only + // process the long list if the current time is greater than _currentAbsoluteThreshold (or + // if the short list is now empty and we need to process the long list to know when to next + // invoke FireNextTimers). + private TimerQueueTimer _shortTimers; + private TimerQueueTimer _longTimers; + + // The current threshold, an absolute time where any timers scheduled to go off at or + // before this time must be queued to the short list. + private int _currentAbsoluteThreshold = ShortTimersThresholdMilliseconds; + + // Default threshold that separates which timers target _shortTimers vs _longTimers. The threshold + // is chosen to balance the number of timers in the small list against the frequency with which + // we need to scan the long list. It's thus somewhat arbitrary and could be changed based on + // observed workload demand. The larger the number, the more timers we'll likely need to enumerate + // every time the timer fires, but also the more likely it is that when it does we won't + // need to look at the long list because the current time will be <= _currentAbsoluteThreshold. + private const int ShortTimersThresholdMilliseconds = 333; + + // Time when Pause was called + private volatile int _pauseTicks = 0; + + // Fire any timers that have expired, and update the native timer to schedule the rest of them. + // We're in a thread pool work item here, and if there are multiple timers to be fired, we want + // to queue all but the first one. The first may can then be invoked synchronously or queued, + // a task left up to our caller, which might be firing timers from multiple queues. + private void FireNextTimers() + { + // We fire the first timer on this thread; any other timers that need to be fired + // are queued to the ThreadPool. + TimerQueueTimer timerToFireOnThisThread = null; + + lock (this) + { + // Since we got here, that means our previous timer has fired. + _isTimerScheduled = false; + bool haveTimerToSchedule = false; + uint nextTimerDuration = uint.MaxValue; + + int nowTicks = TickCount; + + // Sweep through the "short" timers. If the current tick count is greater than + // the current threshold, also sweep through the "long" timers. Finally, as part + // of sweeping the long timers, move anything that'll fire within the next threshold + // to the short list. It's functionally ok if more timers end up in the short list + // than is truly necessary (but not the opposite). + TimerQueueTimer timer = _shortTimers; + for (int listNum = 0; listNum < 2; listNum++) // short == 0, long == 1 + { + while (timer != null) + { + Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "A timer in the list must have a valid due time."); + + // Save off the next timer to examine, in case our examination of this timer results + // in our deleting or moving it; we'll continue after with this saved next timer. + TimerQueueTimer next = timer._next; + + uint elapsed = (uint)(nowTicks - timer._startTicks); + int remaining = (int)timer._dueTime - (int)elapsed; + if (remaining <= 0) + { + // Timer is ready to fire. + + if (timer._period != Timeout.UnsignedInfinite) + { + // This is a repeating timer; schedule it to run again. + + // Discount the extra amount of time that has elapsed since the previous firing time to + // prevent timer ticks from drifting. If enough time has already elapsed for the timer to fire + // again, meaning the timer can't keep up with the short period, have it fire 1 ms from now to + // avoid spinning without a delay. + timer._startTicks = nowTicks; + uint elapsedForNextDueTime = elapsed - timer._dueTime; + timer._dueTime = (elapsedForNextDueTime < timer._period) ? + timer._period - elapsedForNextDueTime : + 1; + + // Update the timer if this becomes the next timer to fire. + if (timer._dueTime < nextTimerDuration) + { + haveTimerToSchedule = true; + nextTimerDuration = timer._dueTime; + } + + // Validate that the repeating timer is still on the right list. It's likely that + // it started in the long list and was moved to the short list at some point, so + // we now want to move it back to the long list if that's where it belongs. Note that + // if we're currently processing the short list and move it to the long list, we may + // end up revisiting it again if we also enumerate the long list, but we will have already + // updated the due time appropriately so that we won't fire it again (it's also possible + // but rare that we could be moving a timer from the long list to the short list here, + // if the initial due time was set to be long but the timer then had a short period). + bool targetShortList = (nowTicks + timer._dueTime) - _currentAbsoluteThreshold <= 0; + if (timer._short != targetShortList) + { + MoveTimerToCorrectList(timer, targetShortList); + } + } + else + { + // Not repeating; remove it from the queue + DeleteTimer(timer); + } + + // If this is the first timer, we'll fire it on this thread (after processing + // all others). Otherwise, queue it to the ThreadPool. + if (timerToFireOnThisThread == null) + { + timerToFireOnThisThread = timer; + } + else + { + ThreadPool.UnsafeQueueUserWorkItemInternal(timer, preferLocal: false); + } + } + else + { + // This timer isn't ready to fire. Update the next time the native timer fires if necessary, + // and move this timer to the short list if its remaining time is now at or under the threshold. + + if (remaining < nextTimerDuration) + { + haveTimerToSchedule = true; + nextTimerDuration = (uint)remaining; + } + + if (!timer._short && remaining <= ShortTimersThresholdMilliseconds) + { + MoveTimerToCorrectList(timer, shortList: true); + } + } + + timer = next; + } + + // Switch to process the long list if necessary. + if (listNum == 0) + { + // Determine how much time remains between now and the current threshold. If time remains, + // we can skip processing the long list. We use > rather than >= because, although we + // know that if remaining == 0 no timers in the long list will need to be fired, we + // don't know without looking at them when we'll need to call FireNextTimers again. We + // could in that case just set the next firing to 1, but we may as well just iterate the + // long list now; otherwise, most timers created in the interim would end up in the long + // list and we'd likely end up paying for another invocation of FireNextTimers that could + // have been delayed longer (to whatever is the current minimum in the long list). + int remaining = _currentAbsoluteThreshold - nowTicks; + if (remaining > 0) + { + if (_shortTimers == null && _longTimers != null) + { + // We don't have any short timers left and we haven't examined the long list, + // which means we likely don't have an accurate nextTimerDuration. + // But we do know that nothing in the long list will be firing before or at _currentAbsoluteThreshold, + // so we can just set nextTimerDuration to the difference between then and now. + nextTimerDuration = (uint)remaining + 1; + haveTimerToSchedule = true; + } + break; + } + + // Switch to processing the long list. + timer = _longTimers; + + // Now that we're going to process the long list, update the current threshold. + _currentAbsoluteThreshold = nowTicks + ShortTimersThresholdMilliseconds; + } + } + + // If we still have scheduled timers, update the timer to ensure it fires + // in time for the next one in line. + if (haveTimerToSchedule) + { + EnsureTimerFiresBy(nextTimerDuration); + } + } + + // Fire the user timer outside of the lock! + timerToFireOnThisThread?.Fire(); + } + + #endregion + + #region Queue implementation + + public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period) + { + int nowTicks = TickCount; + + // The timer can be put onto the short list if it's next absolute firing time + // is <= the current absolute threshold. + int absoluteDueTime = (int)(nowTicks + dueTime); + bool shouldBeShort = _currentAbsoluteThreshold - absoluteDueTime >= 0; + + if (timer._dueTime == Timeout.UnsignedInfinite) + { + // If the timer wasn't previously scheduled, now add it to the right list. + timer._short = shouldBeShort; + LinkTimer(timer); + } + else if (timer._short != shouldBeShort) + { + // If the timer was previously scheduled, but this update should cause + // it to move over the list threshold in either direction, do so. + UnlinkTimer(timer); + timer._short = shouldBeShort; + LinkTimer(timer); + } + + timer._dueTime = dueTime; + timer._period = (period == 0) ? Timeout.UnsignedInfinite : period; + timer._startTicks = nowTicks; + return EnsureTimerFiresBy(dueTime); + } + + public void MoveTimerToCorrectList(TimerQueueTimer timer, bool shortList) + { + Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "Expected timer to be on a list."); + Debug.Assert(timer._short != shortList, "Unnecessary if timer is already on the right list."); + + // Unlink it from whatever list it's on, change its list association, then re-link it. + UnlinkTimer(timer); + timer._short = shortList; + LinkTimer(timer); + } + + private void LinkTimer(TimerQueueTimer timer) + { + // Use timer._short to decide to which list to add. + ref TimerQueueTimer listHead = ref timer._short ? ref _shortTimers : ref _longTimers; + timer._next = listHead; + if (timer._next != null) + { + timer._next._prev = timer; + } + timer._prev = null; + listHead = timer; + } + + private void UnlinkTimer(TimerQueueTimer timer) + { + TimerQueueTimer t = timer._next; + if (t != null) + { + t._prev = timer._prev; + } + + if (_shortTimers == timer) + { + Debug.Assert(timer._short); + _shortTimers = t; + } + else if (_longTimers == timer) + { + Debug.Assert(!timer._short); + _longTimers = t; + } + + t = timer._prev; + if (t != null) + { + t._next = timer._next; + } + + // At this point the timer is no longer in a list, but its next and prev + // references may still point to other nodes. UnlinkTimer should thus be + // followed by something that overwrites those references, either with null + // if deleting the timer or other nodes if adding it to another list. + } + + public void DeleteTimer(TimerQueueTimer timer) + { + if (timer._dueTime != Timeout.UnsignedInfinite) + { + UnlinkTimer(timer); + timer._prev = null; + timer._next = null; + timer._dueTime = Timeout.UnsignedInfinite; + timer._period = Timeout.UnsignedInfinite; + timer._startTicks = 0; + timer._short = false; + } + } + + #endregion + } + + // A timer in our TimerQueue. + internal sealed partial class TimerQueueTimer : IThreadPoolWorkItem + { + // The associated timer queue. + private readonly TimerQueue _associatedTimerQueue; + + // All mutable fields of this class are protected by a lock on _associatedTimerQueue. + // The first six fields are maintained by TimerQueue. + + // Links to the next and prev timers in the list. + internal TimerQueueTimer _next; + internal TimerQueueTimer _prev; + + // true if on the short list; otherwise, false. + internal bool _short; + + // The time, according to TimerQueue.TickCount, when this timer's current interval started. + internal int _startTicks; + + // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from _startTime when we will fire. + internal uint _dueTime; + + // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval. + internal uint _period; + + // Info about the user's callback + private readonly TimerCallback _timerCallback; + private readonly object _state; + private readonly ExecutionContext _executionContext; + + // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only + // after all pending callbacks are complete. We set _canceled to prevent any callbacks that + // are already queued from running. We track the number of callbacks currently executing in + // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning + // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task + // instead of with a provided WaitHandle. + private int _callbacksRunning; + private volatile bool _canceled; + private volatile object _notifyWhenNoCallbacksRunning; // may be either WaitHandle or Task + + + internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, bool flowExecutionContext) + { + _timerCallback = timerCallback; + _state = state; + _dueTime = Timeout.UnsignedInfinite; + _period = Timeout.UnsignedInfinite; + if (flowExecutionContext) + { + _executionContext = ExecutionContext.Capture(); + } + _associatedTimerQueue = TimerQueue.Instances[RuntimeThread.GetCurrentProcessorId() % TimerQueue.Instances.Length]; + + // 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 (_associatedTimerQueue) + { + if (_canceled) + throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + + _period = period; + + if (dueTime == Timeout.UnsignedInfinite) + { + _associatedTimerQueue.DeleteTimer(this); + success = true; + } + else + { + // Don't emit this event during EventPipeController. This avoids initializing FrameworkEventSource during start-up which is expensive relative to the rest of start-up. + if (!EventPipeController.Initializing && FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true, (int)dueTime, (int)period); + + success = _associatedTimerQueue.UpdateTimer(this, dueTime, period); + } + } + + return success; + } + + + public void Close() + { + lock (_associatedTimerQueue) + { + if (!_canceled) + { + _canceled = true; + _associatedTimerQueue.DeleteTimer(this); + } + } + } + + + public bool Close(WaitHandle toSignal) + { + bool success; + bool shouldSignal = false; + + lock (_associatedTimerQueue) + { + if (_canceled) + { + success = false; + } + else + { + _canceled = true; + _notifyWhenNoCallbacksRunning = toSignal; + _associatedTimerQueue.DeleteTimer(this); + shouldSignal = _callbacksRunning == 0; + success = true; + } + } + + if (shouldSignal) + SignalNoCallbacksRunning(); + + return success; + } + + public ValueTask CloseAsync() + { + lock (_associatedTimerQueue) + { + object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning; + + // Mark the timer as canceled if it's not already. + if (_canceled) + { + if (notifyWhenNoCallbacksRunning is WaitHandle) + { + // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with + // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll + // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied + // by the caller, it could be for an auto-reset event or similar where that caller's + // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also + // change the implementation to support storing multiple objects, but that's not pay-for-play, + // and the existing Close(WaitHandle) already discounts this as being invalid, instead just + // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle) + // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we + // simplify by just failing in that case. + return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed))); + } + } + else + { + _canceled = true; + _associatedTimerQueue.DeleteTimer(this); + } + + // We've deleted the timer, so if there are no callbacks queued or running, + // we're done and return an already-completed value task. + if (_callbacksRunning == 0) + { + return default; + } + + Debug.Assert( + notifyWhenNoCallbacksRunning == null || + notifyWhenNoCallbacksRunning is Task); + + // There are callbacks queued or running, so we need to store a Task + // that'll be used to signal the caller when all callbacks complete. Do so as long as + // there wasn't a previous CloseAsync call that did. + if (notifyWhenNoCallbacksRunning == null) + { + var t = new Task((object)null, TaskCreationOptions.RunContinuationsAsynchronously); + _notifyWhenNoCallbacksRunning = t; + return new ValueTask(t); + } + + // A previous CloseAsync call already hooked up a task. Just return it. + return new ValueTask((Task)notifyWhenNoCallbacksRunning); + } + } + + void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true); + + internal void Fire(bool isThreadPool = false) + { + bool canceled = false; + + lock (_associatedTimerQueue) + { + canceled = _canceled; + if (!canceled) + _callbacksRunning++; + } + + if (canceled) + return; + + CallCallback(isThreadPool); + + bool shouldSignal = false; + lock (_associatedTimerQueue) + { + _callbacksRunning--; + if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null) + shouldSignal = true; + } + + if (shouldSignal) + SignalNoCallbacksRunning(); + } + + internal void SignalNoCallbacksRunning() + { + object toSignal = _notifyWhenNoCallbacksRunning; + Debug.Assert(toSignal is WaitHandle || toSignal is Task); + + if (toSignal is WaitHandle wh) + { + EventWaitHandle.Set(wh.SafeWaitHandle); + } + else + { + ((Task)toSignal).TrySetResult(true); + } + } + + internal void CallCallback(bool isThreadPool) + { + 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 + ExecutionContext context = _executionContext; + if (context == null) + { + _timerCallback(_state); + } + else + { + if (isThreadPool) + { + ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this); + } + else + { + ExecutionContext.RunInternal(context, s_callCallbackInContext, this); + } + } + } + + private static readonly ContextCallback s_callCallbackInContext = state => + { + TimerQueueTimer t = (TimerQueueTimer)state; + t._timerCallback(t._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. + internal sealed class TimerHolder + { + internal TimerQueueTimer _timer; + + public TimerHolder(TimerQueueTimer timer) + { + _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 anymore. + if (Environment.HasShutdownStarted) + return; + + _timer.Close(); + } + + public void Close() + { + _timer.Close(); + GC.SuppressFinalize(this); + } + + public bool Close(WaitHandle notifyObject) + { + bool result = _timer.Close(notifyObject); + GC.SuppressFinalize(this); + return result; + } + + public ValueTask CloseAsync() + { + ValueTask result = _timer.CloseAsync(); + GC.SuppressFinalize(this); + return result; + } + } + + + public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable + { + private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe; + + private TimerHolder _timer; + + public Timer(TimerCallback callback, + object state, + int dueTime, + int period) : + this(callback, state, dueTime, period, flowExecutionContext: true) + { + } + + internal Timer(TimerCallback callback, + object state, + int dueTime, + int period, + bool flowExecutionContext) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (period < -1) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + + TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext); + } + + public Timer(TimerCallback callback, + object state, + TimeSpan dueTime, + TimeSpan period) + { + long dueTm = (long)dueTime.TotalMilliseconds; + if (dueTm < -1) + throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (dueTm > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge); + + long periodTm = (long)period.TotalMilliseconds; + if (periodTm < -1) + throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (periodTm > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge); + + TimerSetup(callback, state, (uint)dueTm, (uint)periodTm); + } + + [CLSCompliant(false)] + public Timer(TimerCallback callback, + object state, + uint dueTime, + uint period) + { + TimerSetup(callback, state, dueTime, period); + } + + public Timer(TimerCallback callback, + object state, + long dueTime, + long period) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (period < -1) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (dueTime > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); + if (period > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); + TimerSetup(callback, state, (uint)dueTime, (uint)period); + } + + 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). + + TimerSetup(callback, this, (uint)dueTime, (uint)period); + } + + private void TimerSetup(TimerCallback callback, + object state, + uint dueTime, + uint period, + bool flowExecutionContext = true) + { + if (callback == null) + throw new ArgumentNullException(nameof(TimerCallback)); + + _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext)); + } + + public bool Change(int dueTime, int period) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (period < -1) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + + return _timer._timer.Change((uint)dueTime, (uint)period); + } + + public bool Change(TimeSpan dueTime, TimeSpan period) + { + return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds); + } + + [CLSCompliant(false)] + public bool Change(uint dueTime, uint period) + { + return _timer._timer.Change(dueTime, period); + } + + public bool Change(long dueTime, long period) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (period < -1) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + if (dueTime > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); + if (period > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); + + return _timer._timer.Change((uint)dueTime, (uint)period); + } + + public bool Dispose(WaitHandle notifyObject) + { + if (notifyObject == null) + throw new ArgumentNullException(nameof(notifyObject)); + + return _timer.Close(notifyObject); + } + + public void Dispose() + { + _timer.Close(); + } + + public ValueTask DisposeAsync() + { + return _timer.CloseAsync(); + } + } +} From fc4fd25e98b78e20603858213029337dfca45380 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 27 Jan 2019 14:15:08 -0500 Subject: [PATCH 2/4] Remove several uses of VoidTaskResult (dotnet/coreclr#22238) * Remove defunct netstandard code from ValueTask * Remove several uses of VoidTaskResult Currently TrySetResult/Canceled/Exception live on `Task`. There's no reason `TrySetCanceled` and `TrySetException` need to live there, as they only access state from the base `Task`, and so can be moved down. `TrySetResult` needs the `TResult`, however in a variety of cases `Task` is used with a `VoidTaskResult`, and for such cases we can just have a parameterless `TrySetResult()` on the base class as well, which can be used any time there is no `TResult` or when `default(TResult)` is the desired result. This lets us switch several cases where we were using `Task` to just be `Task`, which saves 8 bytes on the task instance on 64-bit. It also avoids an Interlocked.Exchange as part of the TrySetResult call. This primarily affects Task.Delay and the non-generic variants of Task.WhenAll, ValueTask.AsTask(), Task.FromCanceled, and Task.FromException. Signed-off-by: dotnet-bot --- .../shared/System/IO/MemoryStream.cs | 8 +- .../CompilerServices/AsyncMethodBuilder.cs | 2 +- .../AsyncValueTaskMethodBuilder.cs | 8 - .../Tasks/ConcurrentExclusiveSchedulerPair.cs | 10 +- .../shared/System/Threading/Tasks/Future.cs | 95 +---------- .../System/Threading/Tasks/FutureFactory.cs | 8 +- .../shared/System/Threading/Tasks/Task.cs | 147 ++++++++++++++++-- .../System/Threading/Tasks/ValueTask.cs | 122 ++------------- 8 files changed, 168 insertions(+), 232 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs index b9f2b5e35fc..3dfc9aef445 100644 --- a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs @@ -414,7 +414,7 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } catch (OperationCanceledException oce) { - return Task.FromCancellation(oce); + return Task.FromCanceled(oce); } catch (Exception exception) { @@ -450,7 +450,7 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken } catch (OperationCanceledException oce) { - return new ValueTask(Task.FromCancellation(oce)); + return new ValueTask(Task.FromCanceled(oce)); } catch (Exception exception) { @@ -739,7 +739,7 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati } catch (OperationCanceledException oce) { - return Task.FromCancellation(oce); + return Task.FromCanceled(oce); } catch (Exception exception) { @@ -770,7 +770,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo } catch (OperationCanceledException oce) { - return new ValueTask(Task.FromCancellation(oce)); + return new ValueTask(Task.FromCanceled(oce)); } catch (Exception exception) { diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index 08802ee5466..ef5d609a800 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -190,7 +190,7 @@ public struct AsyncTaskMethodBuilder #if PROJECTN private static readonly Task s_cachedCompleted = AsyncTaskCache.CreateCacheableTask(default(VoidTaskResult)); #else - private readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask; + private static readonly Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask; #endif /// The generic builder object to which this non-generic instance delegates. diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 0e1220d1190..b39ff7604b3 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -38,11 +38,7 @@ public static AsyncValueTaskMethodBuilder Create() => [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => // will provide the right ExecutionContext semantics -#if netstandard - _methodBuilder.Start(ref stateMachine); -#else AsyncMethodBuilderCore.Start(ref stateMachine); -#endif /// Associates the builder with the specified state machine. /// The state machine instance to associate with the builder. @@ -143,11 +139,7 @@ public static AsyncValueTaskMethodBuilder Create() => [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => // will provide the right ExecutionContext semantics -#if netstandard - _methodBuilder.Start(ref stateMachine); -#else AsyncMethodBuilderCore.Start(ref stateMachine); -#endif /// Associates the builder with the specified state machine. /// The state machine instance to associate with the builder. diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs index 9adcdd6df9b..e207a7421f7 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs @@ -147,7 +147,7 @@ public void Complete() public Task Completion { // ValueLock not needed, but it's ok if it's held - get { return EnsureCompletionStateInitialized().Task; } + get { return EnsureCompletionStateInitialized(); } } /// Gets the lazily-initialized completion state. @@ -214,12 +214,12 @@ private void CompleteTaskAsync() ThreadPool.QueueUserWorkItem(state => { var localThis = (ConcurrentExclusiveSchedulerPair)state; - Debug.Assert(!localThis.m_completionState.Task.IsCompleted, "Completion should only happen once."); + Debug.Assert(!localThis.m_completionState.IsCompleted, "Completion should only happen once."); List exceptions = localThis.m_completionState.m_exceptions; bool success = (exceptions != null && exceptions.Count > 0) ? localThis.m_completionState.TrySetException(exceptions) : - localThis.m_completionState.TrySetResult(default); + localThis.m_completionState.TrySetResult(); Debug.Assert(success, "Expected to complete completion task."); localThis.m_threadProcessingMode.Dispose(); @@ -479,7 +479,7 @@ private void ProcessConcurrentTasks() /// the Completion. /// [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - private sealed class CompletionState : TaskCompletionSource + private sealed class CompletionState : Task { /// Whether the scheduler has had completion requested. /// This variable is not volatile, so to gurantee safe reading reads, Volatile.Read is used in TryExecuteTaskInline. @@ -741,7 +741,7 @@ private ProcessingMode ModeForDebugger get { // If our completion task is done, so are we. - if (m_completionState != null && m_completionState.Task.IsCompleted) return ProcessingMode.Completed; + if (m_completionState != null && m_completionState.IsCompleted) return ProcessingMode.Completed; // Otherwise, summarize our current state. var mode = ProcessingMode.NotCurrentlyProcessing; diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs index 5cc0b6737b5..06eb1186ae2 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs @@ -386,6 +386,8 @@ internal bool TrySetResult(TResult result) { Debug.Assert(m_action == null, "Task.TrySetResult(): non-null m_action"); + bool returnValue = false; + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation // has been made, (2) The result has not already been set, (3) An exception has not previously // been recorded, and (4) Cancellation has not been requested. @@ -411,10 +413,10 @@ internal bool TrySetResult(TResult result) props.SetCompleted(); } FinishContinuations(); - return true; + returnValue = true; } - return false; + return returnValue; } // Transitions the promise task into a successfully completed state with the specified result. @@ -491,95 +493,6 @@ internal TResult GetResultCore(bool waitCompletionNotification) return m_result; } - // Allow multiple exceptions to be assigned to a promise-style task. - // This is useful when a TaskCompletionSource stands in as a proxy - // for a "real" task (as we do in Unwrap(), ContinueWhenAny() and ContinueWhenAll()) - // and the "real" task ends up with multiple exceptions, which is possible when - // a task has children. - // - // Called from TaskCompletionSource.SetException(IEnumerable). - internal bool TrySetException(object exceptionObject) - { - Debug.Assert(m_action == null, "Task.TrySetException(): non-null m_action"); - - // TCS.{Try}SetException() should have checked for this - Debug.Assert(exceptionObject != null, "Expected non-null exceptionObject argument"); - - // Only accept these types. - Debug.Assert( - (exceptionObject is Exception) || (exceptionObject is IEnumerable) || - (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable), - "Expected exceptionObject to be either Exception, ExceptionDispatchInfo, or IEnumerable<> of one of those"); - - bool returnValue = false; - - // "Reserve" the completion for this task, while making sure that: (1) No prior reservation - // has been made, (2) The result has not already been set, (3) An exception has not previously - // been recorded, and (4) Cancellation has not been requested. - // - // If the reservation is successful, then add the exception(s) and finish completion processing. - // - // The lazy initialization may not be strictly necessary, but I'd like to keep it here - // anyway. Some downstream logic may depend upon an inflated m_contingentProperties. - EnsureContingentPropertiesInitialized(); - if (AtomicStateUpdate(TASK_STATE_COMPLETION_RESERVED, - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) - { - AddException(exceptionObject); // handles singleton exception or exception collection - Finish(false); - returnValue = true; - } - - return returnValue; - } - - // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder - // If the tokenToRecord is not None, it will be stored onto the task. - // This method is only valid for promise tasks. - internal bool TrySetCanceled(CancellationToken tokenToRecord) - { - return TrySetCanceled(tokenToRecord, null); - } - - // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder - // If the tokenToRecord is not None, it will be stored onto the task. - // If the OperationCanceledException is not null, it will be stored into the task's exception holder. - // This method is only valid for promise tasks. - internal bool TrySetCanceled(CancellationToken tokenToRecord, object cancellationException) - { - Debug.Assert(m_action == null, "Task.TrySetCanceled(): non-null m_action"); -#if DEBUG - var ceAsEdi = cancellationException as ExceptionDispatchInfo; - Debug.Assert( - cancellationException == null || - cancellationException is OperationCanceledException || - (ceAsEdi != null && ceAsEdi.SourceException is OperationCanceledException), - "Expected null or an OperationCanceledException"); -#endif - - bool returnValue = false; - - // "Reserve" the completion for this task, while making sure that: (1) No prior reservation - // has been made, (2) The result has not already been set, (3) An exception has not previously - // been recorded, and (4) Cancellation has not been requested. - // - // If the reservation is successful, then record the cancellation and finish completion processing. - // - // Note: I had to access static Task variables through Task - // instead of Task, because I have a property named Task and that - // was confusing the compiler. - if (AtomicStateUpdate(Task.TASK_STATE_COMPLETION_RESERVED, - Task.TASK_STATE_COMPLETION_RESERVED | Task.TASK_STATE_CANCELED | - Task.TASK_STATE_FAULTED | Task.TASK_STATE_RAN_TO_COMPLETION)) - { - RecordInternalCancellationRequest(tokenToRecord, cancellationException); - CancellationCleanupLogic(); // perform cancellation cleanup actions - returnValue = true; - } - - return returnValue; - } - /// /// Provides access to factory methods for creating instances. /// diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs index 0d8cdcb535a..55fab9a5de3 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs @@ -800,7 +800,7 @@ internal static Task FromAsyncImpl(Func FromAsyncImpl(Func FromAsyncImpl(Func FromAsyncImpl(FuncCompletes a promise task as RanToCompletion. + /// If this is a Task{T}, default(T) is the implied result. + /// true if the task was transitioned to ran to completion; false if it was already completed. + internal bool TrySetResult() + { + Debug.Assert(m_action == null, "Task.TrySetResult(): non-null m_action"); + + if (AtomicStateUpdate( + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION, + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + { + ContingentProperties props = m_contingentProperties; + if (props != null) + { + NotifyParentIfPotentiallyAttachedTask(); + props.SetCompleted(); + } + FinishContinuations(); + return true; + } + + return false; + } + + // Allow multiple exceptions to be assigned to a promise-style task. + // This is useful when a TaskCompletionSource stands in as a proxy + // for a "real" task (as we do in Unwrap(), ContinueWhenAny() and ContinueWhenAll()) + // and the "real" task ends up with multiple exceptions, which is possible when + // a task has children. + // + // Called from TaskCompletionSource.SetException(IEnumerable). + internal bool TrySetException(object exceptionObject) + { + Debug.Assert(m_action == null, "Task.TrySetException(): non-null m_action"); + + // TCS.{Try}SetException() should have checked for this + Debug.Assert(exceptionObject != null, "Expected non-null exceptionObject argument"); + + // Only accept these types. + Debug.Assert( + (exceptionObject is Exception) || (exceptionObject is IEnumerable) || + (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable), + "Expected exceptionObject to be either Exception, ExceptionDispatchInfo, or IEnumerable<> of one of those"); + + bool returnValue = false; + + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation + // has been made, (2) The result has not already been set, (3) An exception has not previously + // been recorded, and (4) Cancellation has not been requested. + // + // If the reservation is successful, then add the exception(s) and finish completion processing. + // + // The lazy initialization may not be strictly necessary, but I'd like to keep it here + // anyway. Some downstream logic may depend upon an inflated m_contingentProperties. + EnsureContingentPropertiesInitialized(); + if (AtomicStateUpdate( + TASK_STATE_COMPLETION_RESERVED, + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + { + AddException(exceptionObject); // handles singleton exception or exception collection + Finish(false); + returnValue = true; + } + + return returnValue; + } + + // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder + // If the tokenToRecord is not None, it will be stored onto the task. + // This method is only valid for promise tasks. + internal bool TrySetCanceled(CancellationToken tokenToRecord) + { + return TrySetCanceled(tokenToRecord, null); + } + + // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder + // If the tokenToRecord is not None, it will be stored onto the task. + // If the OperationCanceledException is not null, it will be stored into the task's exception holder. + // This method is only valid for promise tasks. + internal bool TrySetCanceled(CancellationToken tokenToRecord, object cancellationException) + { + Debug.Assert(m_action == null, "Task.TrySetCanceled(): non-null m_action"); + Debug.Assert( + cancellationException == null || + cancellationException is OperationCanceledException || + (cancellationException as ExceptionDispatchInfo)?.SourceException is OperationCanceledException, + "Expected null or an OperationCanceledException"); + + bool returnValue = false; + + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation + // has been made, (2) The result has not already been set, (3) An exception has not previously + // been recorded, and (4) Cancellation has not been requested. + // + // If the reservation is successful, then record the cancellation and finish completion processing. + if (AtomicStateUpdate( + TASK_STATE_COMPLETION_RESERVED, + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION)) + { + RecordInternalCancellationRequest(tokenToRecord, cancellationException); + CancellationCleanupLogic(); // perform cancellation cleanup actions + returnValue = true; + } + + return returnValue; + } + // // Continuation passing functionality (aka ContinueWith) @@ -5054,7 +5161,12 @@ public static Task FromResult(TResult result) /// The faulted task. public static Task FromException(Exception exception) { - return FromException(exception); + if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + + var task = new Task(); + bool succeeded = task.TrySetException(exception); + Debug.Assert(succeeded, "This should always succeed on a new task."); + return task; } /// Creates a that's completed exceptionally with the specified exception. @@ -5092,13 +5204,26 @@ public static Task FromCanceled(CancellationToken cancellation return new Task(true, default, TaskCreationOptions.None, cancellationToken); } + /// Creates a that's completed due to cancellation with the specified exception. + /// The exception with which to complete the task. + /// The canceled task. + internal static Task FromCanceled(OperationCanceledException exception) + { + Debug.Assert(exception != null); + + var task = new Task(); + bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception); + Debug.Assert(succeeded, "This should always succeed on a new task."); + return task; + } + /// Creates a that's completed due to cancellation with the specified exception. /// The type of the result returned by the task. /// The exception with which to complete the task. /// The canceled task. - internal static Task FromCancellation(OperationCanceledException exception) + internal static Task FromCanceled(OperationCanceledException exception) { - if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + Debug.Assert(exception != null); var task = new Task(); bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception); @@ -5389,10 +5514,9 @@ public static Task Delay(int millisecondsDelay, CancellationToken cancellationTo } /// Task that also stores the completion closure and logic for Task.Delay implementation. - private sealed class DelayPromise : Task + private sealed class DelayPromise : Task { internal DelayPromise(CancellationToken token) - : base() { this.Token = token; if (AsyncCausalityTracer.LoggingOn) @@ -5423,7 +5547,7 @@ internal void Complete() if (s_asyncDebuggingEnabled) RemoveFromActiveTasks(this); - setSucceeded = TrySetResult(default); + setSucceeded = TrySetResult(); } // If we set the value, also clean up. @@ -5555,10 +5679,10 @@ private static Task InternalWhenAll(Task[] tasks) new WhenAllPromise(tasks); } - // A Task that gets completed when all of its constituent tasks complete. + // A Task that gets completed when all of its constituent tasks complete. // Completion logic will analyze the antecedents in order to choose completion status. // This type allows us to replace this logic: - // Task promise = new Task(...); + // Task promise = new Task(...); // Action completionAction = delegate { }; // TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction); // return promise; @@ -5567,7 +5691,7 @@ private static Task InternalWhenAll(Task[] tasks) // which saves a couple of allocations and enables debugger notification specialization. // // Used in InternalWhenAll(Task[]) - private sealed class WhenAllPromise : Task, ITaskCompletionAction + private sealed class WhenAllPromise : Task, ITaskCompletionAction { /// /// Stores all of the constituent tasks. Tasks clear themselves out of this @@ -5577,8 +5701,7 @@ private sealed class WhenAllPromise : Task, ITaskCompletionActio /// The number of tasks remaining to complete. private int m_count; - internal WhenAllPromise(Task[] tasks) : - base() + internal WhenAllPromise(Task[] tasks) { Debug.Assert(tasks != null, "Expected a non-null task array"); Debug.Assert(tasks.Length > 0, "Expected a non-zero length task array"); @@ -5656,7 +5779,7 @@ public void Invoke(Task completedTask) if (s_asyncDebuggingEnabled) RemoveFromActiveTasks(this); - TrySetResult(default); + TrySetResult(); } } Debug.Assert(m_count >= 0, "Count should never go below 0"); diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs index 4d6a7596058..256d4d71a2c 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs @@ -7,10 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks.Sources; - -#if !netstandard using Internal.Runtime.CompilerServices; -#endif namespace System.Threading.Tasks { @@ -60,19 +57,10 @@ namespace System.Threading.Tasks public readonly struct ValueTask : IEquatable { /// A task canceled using `new CancellationToken(true)`. - private static readonly Task s_canceledTask = -#if netstandard - Task.Delay(Timeout.Infinite, new CancellationToken(canceled: true)); -#else - Task.FromCanceled(new CancellationToken(canceled: true)); -#endif + private static readonly Task s_canceledTask = Task.FromCanceled(new CancellationToken(canceled: true)); + /// A successfully completed task. - internal static Task CompletedTask -#if netstandard - { get; } = Task.Delay(0); -#else - => Task.CompletedTask; -#endif + internal static Task CompletedTask => Task.CompletedTask; /// null if representing a successful synchronous completion, otherwise a or a . internal readonly object _obj; @@ -190,45 +178,27 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t) { if (status == ValueTaskSourceStatus.Canceled) { -#if !netstandard if (exc is OperationCanceledException oce) { - var task = new Task(); + var task = new Task(); task.TrySetCanceled(oce.CancellationToken, oce); return task; } -#endif + return s_canceledTask; } else { -#if netstandard - var tcs = new TaskCompletionSource(); - tcs.TrySetException(exc); - return tcs.Task; -#else return Task.FromException(exc); -#endif } } } - var m = new ValueTaskSourceAsTask(t, _token); - return -#if netstandard - m.Task; -#else - m; -#endif + return new ValueTaskSourceAsTask(t, _token); } /// Type used to create a to represent a . - private sealed class ValueTaskSourceAsTask : -#if netstandard - TaskCompletionSource -#else - Task -#endif + private sealed class ValueTaskSourceAsTask : Task { private static readonly Action s_completionAction = state => { @@ -247,15 +217,12 @@ private sealed class ValueTaskSourceAsTask : try { source.GetResult(vtst._token); - vtst.TrySetResult(default); + vtst.TrySetResult(); } catch (Exception exc) { if (status == ValueTaskSourceStatus.Canceled) { -#if netstandard - vtst.TrySetCanceled(); -#else if (exc is OperationCanceledException oce) { vtst.TrySetCanceled(oce.CancellationToken, oce); @@ -264,7 +231,6 @@ private sealed class ValueTaskSourceAsTask : { vtst.TrySetCanceled(new CancellationToken(true)); } -#endif } else { @@ -325,12 +291,7 @@ public bool IsCompletedSuccessfully if (obj is Task t) { - return -#if netstandard - t.Status == TaskStatus.RanToCompletion; -#else - t.IsCompletedSuccessfully; -#endif + return t.IsCompletedSuccessfully; } return Unsafe.As(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; @@ -398,11 +359,7 @@ internal void ThrowIfCompletedUnsuccessfully() { if (obj is Task t) { -#if netstandard - t.GetAwaiter().GetResult(); -#else TaskAwaiter.ValidateEnd(t); -#endif } else { @@ -569,12 +526,7 @@ public Task AsTask() if (obj == null) { - return -#if netstandard - Task.FromResult(_result); -#else - AsyncTaskMethodBuilder.GetTaskForResult(_result); -#endif + return AsyncTaskMethodBuilder.GetTaskForResult(_result); } if (obj is Task t) @@ -602,12 +554,7 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t) { // Get the result of the operation and return a task for it. // If any exception occurred, propagate it - return -#if netstandard - Task.FromResult(t.GetResult(_token)); -#else - AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult(_token)); -#endif + return AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult(_token)); // If status is Faulted or Canceled, GetResult should throw. But // we can't guarantee every implementation will do the "right thing". @@ -618,59 +565,33 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t) { if (status == ValueTaskSourceStatus.Canceled) { -#if !netstandard if (exc is OperationCanceledException oce) { var task = new Task(); task.TrySetCanceled(oce.CancellationToken, oce); return task; } -#endif Task canceledTask = s_canceledTask; if (canceledTask == null) { -#if netstandard - var tcs = new TaskCompletionSource(); - tcs.TrySetCanceled(); - canceledTask = tcs.Task; -#else - canceledTask = Task.FromCanceled(new CancellationToken(true)); -#endif // Benign race condition to initialize cached task, as identity doesn't matter. - s_canceledTask = canceledTask; + s_canceledTask = Task.FromCanceled(new CancellationToken(true)); } return canceledTask; } else { -#if netstandard - var tcs = new TaskCompletionSource(); - tcs.TrySetException(exc); - return tcs.Task; -#else return Task.FromException(exc); -#endif } } } - var m = new ValueTaskSourceAsTask(t, _token); - return -#if netstandard - m.Task; -#else - m; -#endif + return new ValueTaskSourceAsTask(t, _token); } /// Type used to create a to represent a . - private sealed class ValueTaskSourceAsTask : -#if netstandard - TaskCompletionSource -#else - Task -#endif + private sealed class ValueTaskSourceAsTask : Task { private static readonly Action s_completionAction = state => { @@ -694,9 +615,6 @@ private sealed class ValueTaskSourceAsTask : { if (status == ValueTaskSourceStatus.Canceled) { -#if netstandard - vtst.TrySetCanceled(); -#else if (exc is OperationCanceledException oce) { vtst.TrySetCanceled(oce.CancellationToken, oce); @@ -705,7 +623,6 @@ private sealed class ValueTaskSourceAsTask : { vtst.TrySetCanceled(new CancellationToken(true)); } -#endif } else { @@ -766,12 +683,7 @@ public bool IsCompletedSuccessfully if (obj is Task t) { - return -#if netstandard - t.Status == TaskStatus.RanToCompletion; -#else - t.IsCompletedSuccessfully; -#endif + return t.IsCompletedSuccessfully; } return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; @@ -843,12 +755,8 @@ public TResult Result if (obj is Task t) { -#if netstandard - return t.GetAwaiter().GetResult(); -#else TaskAwaiter.ValidateEnd(t); return t.ResultOnSuccess; -#endif } return Unsafe.As>(obj).GetResult(_token); From 149b6b09039aa177ec3ba73a1477f0c37f7f6d2f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 26 Jan 2019 15:38:26 +0100 Subject: [PATCH 3/4] Move Timer to shared CoreLib partition. Update to code from CoreCLR PR, add EventWaitHandle.Set static method. --- .../src/System.Private.CoreLib.csproj | 1 - .../Tracing/FrameworkEventSource.cs | 10 + .../System/Threading/EventWaitHandle.Unix.cs | 14 + .../src/System/Threading/Timer.Unix.cs | 40 +- .../src/System/Threading/Timer.Windows.cs | 33 +- .../src/System/Threading/Timer.cs | 707 ------------------ 6 files changed, 44 insertions(+), 761 deletions(-) delete mode 100644 src/System.Private.CoreLib/src/System/Threading/Timer.cs diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 57efc1cff09..64ac952c752 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -292,7 +292,6 @@ - diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs index 0298b20e67c..d9aecc66b90 100644 --- a/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs +++ b/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs @@ -19,6 +19,8 @@ public static class Keywords public const EventKeywords ThreadTransfer = (EventKeywords)0x0010; } + public static bool IsInitialized { get => true; } + public bool IsEnabled(EventLevel level, EventKeywords keywords) { return false; @@ -31,6 +33,14 @@ public void ThreadPoolEnqueueWorkObject(object workID) public void ThreadPoolDequeueWorkObject(object workID) { } + + public void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues, int intInfo1, int intInfo2) + { + } + + public void ThreadTransferReceiveObj(object id, int kind, string info) + { + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs index 5f1aeb3e40b..7abcf5bf88f 100644 --- a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs +++ b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs @@ -53,6 +53,20 @@ public bool Set() } } + internal static bool Set(SafeWaitHandle waitHandle) + { + waitHandle.DangerousAddRef(); + try + { + WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle()); + return true; + } + finally + { + waitHandle.DangerousRelease(); + } + } + private SafeWaitHandle ValidateHandle() { // The field value is modifiable via the public property, save it locally diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs index 958f13b7281..9a51dfbd057 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs @@ -26,11 +26,12 @@ internal partial class TimerQueue /// private static volatile int s_nextTimerDuration; - private void SetTimer(uint actualDuration) + private TimerQueue(int id) { - // This function is called with the TimerQueue lock acquired - Debug.Assert(Lock.IsAcquired); + } + private bool SetTimer(uint actualDuration) + { // 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); @@ -50,6 +51,8 @@ private void SetTimer(uint actualDuration) { s_timerEvent.Set(); } + + return true; } @@ -57,7 +60,7 @@ private void SetTimer(uint actualDuration) /// 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. /// - private static void TimerThread() + private void TimerThread() { int currentTimerInterval; @@ -92,7 +95,7 @@ private static void TimerThread() // Check whether TimerQueue needs to process expired timers. if (timerHasExpired) { - Instance.FireNextTimers(); + 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. @@ -116,31 +119,4 @@ private static int TickCount } } } - - internal sealed partial class TimerQueueTimer - { - private void SignalNoCallbacksRunning() - { - object toSignal = _notifyWhenNoCallbacksRunning; - Debug.Assert(toSignal is WaitHandle || toSignal is Task); - - if (toSignal is WaitHandle wh) - { - SafeWaitHandle waitHandle = wh.SafeWaitHandle; - waitHandle.DangerousAddRef(); - try - { - WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle()); - } - finally - { - waitHandle.DangerousRelease(); - } - } - else - { - ((Task)toSignal).TrySetResult(true); - } - } - } } diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs index 49ccf13f844..7123bc0ec83 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs @@ -14,22 +14,29 @@ namespace System.Threading internal partial class TimerQueue { private IntPtr _nativeTimer; + private readonly int _id; + + private TimerQueue(int id) + { + _id = id; + } [NativeCallable(CallingConvention = CallingConvention.StdCall)] private static void TimerCallback(IntPtr instance, IntPtr context, IntPtr timer) { + int id = (int)context; var wrapper = ThreadPoolCallbackWrapper.Enter(); - Instance.FireNextTimers(); + Instances[id].FireNextTimers(); wrapper.Exit(); } - private unsafe void SetTimer(uint actualDuration) + private unsafe bool SetTimer(uint actualDuration) { if (_nativeTimer == IntPtr.Zero) { IntPtr nativeCallback = AddrofIntrinsics.AddrOf(TimerCallback); - _nativeTimer = Interop.mincore.CreateThreadpoolTimer(nativeCallback, IntPtr.Zero, IntPtr.Zero); + _nativeTimer = Interop.mincore.CreateThreadpoolTimer(nativeCallback, (IntPtr)_id, IntPtr.Zero); if (_nativeTimer == IntPtr.Zero) throw new OutOfMemoryException(); } @@ -37,6 +44,8 @@ private unsafe void SetTimer(uint actualDuration) // Negative time indicates the amount of time to wait relative to the current time, in 100 nanosecond units long dueTime = -10000 * (long)actualDuration; Interop.mincore.SetThreadpoolTimer(_nativeTimer, &dueTime, 0, 0); + + return true; } // @@ -62,22 +71,4 @@ private static int TickCount } } } - - internal sealed partial class TimerQueueTimer - { - private void SignalNoCallbacksRunning() - { - object toSignal = _notifyWhenNoCallbacksRunning; - Debug.Assert(toSignal is WaitHandle || toSignal is Task); - - if (toSignal is WaitHandle wh) - { - Interop.Kernel32.SetEvent(wh.SafeWaitHandle); - } - else - { - ((Task)toSignal).TrySetResult(true); - } - } - } } diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.cs deleted file mode 100644 index 522e7176b71..00000000000 --- a/src/System.Private.CoreLib/src/System/Threading/Timer.cs +++ /dev/null @@ -1,707 +0,0 @@ -// 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.Diagnostics; -using System.Threading.Tasks; - -namespace System.Threading -{ - public delegate void TimerCallback(object state); - - // - // TimerQueue maintains a list of active timers in this AppDomain. We use a single native timer to schedule - // all managed timers in the process. - // - // 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. - // - internal partial class TimerQueue - { - #region singleton pattern implementation - - // The one-and-only TimerQueue for the AppDomain. - private 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 - - private int _currentNativeTimerStartTicks; - private uint _currentNativeTimerDuration = uint.MaxValue; - - private void EnsureAppDomainTimerFiresBy(uint requestedDuration) - { - // - // The CLR 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 (_currentNativeTimerDuration != uint.MaxValue) - { - uint elapsed = (uint)(TickCount - _currentNativeTimerStartTicks); - if (elapsed >= _currentNativeTimerDuration) - return; //the timer's about to fire - - uint remainingDuration = _currentNativeTimerDuration - elapsed; - if (actualDuration >= remainingDuration) - return; //the timer will fire earlier than this request - } - - SetTimer(actualDuration); - _currentNativeTimerDuration = actualDuration; - - _currentNativeTimerStartTicks = TickCount; - } - - #endregion - - #region Firing timers - - // - // The list of timers - // - private TimerQueueTimer _timers; - readonly internal Lock Lock = new Lock(); - - // - // 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; - - using (LockHolder.Hold(Lock)) - { - // - // since we got here, that means our previous timer has fired. - // - _currentNativeTimerDuration = uint.MaxValue; - - 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 = _timers; - while (timer != null) - { - Debug.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; - uint elapsedForNextDueTime = elapsed - timer.m_dueTime; - if (elapsedForNextDueTime < timer.m_period) - { - // Discount the extra time that has elapsed since the previous firing - // to prevent the timer ticks from drifting - timer.m_dueTime = timer.m_period - elapsedForNextDueTime; - } - else - { - // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up - // with the short period, have it fire 1 ms from now to avoid spnning without delay. - timer.m_dueTime = 1; - } - - // - // 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(); - } - - 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 = _timers; - timer.m_prev = null; - if (timer.m_next != null) - timer.m_next.m_prev = timer; - _timers = timer; - } - timer.m_dueTime = dueTime; - timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period; - timer.m_startTicks = TickCount; - EnsureAppDomainTimerFiresBy(dueTime); - return true; - } - - 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 (_timers == timer) - _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. - // - internal sealed partial 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 - // - private readonly TimerCallback _timerCallback; - private readonly object _state; - private readonly ExecutionContext _executionContext; - - - // - // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only - // after all pending callbacks are complete. We set _canceled to prevent any callbacks that - // are already queued from running. We track the number of callbacks currently executing in - // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning - // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task - // instead of with a provided WaitHandle. - private int _callbacksRunning; - private volatile bool _canceled; - private volatile object _notifyWhenNoCallbacksRunning; - - - internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, bool flowExecutionContext) - { - _timerCallback = timerCallback; - _state = state; - m_dueTime = Timeout.UnsignedInfinite; - m_period = Timeout.UnsignedInfinite; - if (flowExecutionContext) - { - _executionContext = ExecutionContext.Capture(); - } - - // - // 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; - - using (LockHolder.Hold(TimerQueue.Instance.Lock)) - { - if (_canceled) - throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); - - m_period = period; - - if (dueTime == Timeout.UnsignedInfinite) - { - TimerQueue.Instance.DeleteTimer(this); - success = true; - } - else - { - success = TimerQueue.Instance.UpdateTimer(this, dueTime, period); - } - } - - return success; - } - - - public void Close() - { - using (LockHolder.Hold(TimerQueue.Instance.Lock)) - { - if (!_canceled) - { - _canceled = true; - TimerQueue.Instance.DeleteTimer(this); - } - } - } - - - public bool Close(WaitHandle toSignal) - { - bool success; - bool shouldSignal = false; - - using (LockHolder.Hold(TimerQueue.Instance.Lock)) - { - if (_canceled) - { - success = false; - } - else - { - _canceled = true; - _notifyWhenNoCallbacksRunning = toSignal; - TimerQueue.Instance.DeleteTimer(this); - shouldSignal = _callbacksRunning == 0; - success = true; - } - } - - if (shouldSignal) - SignalNoCallbacksRunning(); - - return success; - } - - public ValueTask CloseAsync() - { - using (LockHolder.Hold(TimerQueue.Instance.Lock)) - { - object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning; - - // Mark the timer as canceled if it's not already. - if (_canceled) - { - if (notifyWhenNoCallbacksRunning is WaitHandle) - { - // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with - // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll - // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied - // by the caller, it could be for an auto-reset event or similar where that caller's - // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also - // change the implementation to support storing multiple objects, but that's not pay-for-play, - // and the existing Close(WaitHandle) already discounts this as being invalid, instead just - // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle) - // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we - // simplify by just failing in that case. - return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed))); - } - } - else - { - _canceled = true; - TimerQueue.Instance.DeleteTimer(this); - } - - // We've deleted the timer, so if there are no callbacks queued or running, - // we're done and return an already-completed value task. - if (_callbacksRunning == 0) - { - return default; - } - - Debug.Assert( - notifyWhenNoCallbacksRunning == null || - notifyWhenNoCallbacksRunning is Task); - - // There are callbacks queued or running, so we need to store a Task - // that'll be used to signal the caller when all callbacks complete. Do so as long as - // there wasn't a previous CloseAsync call that did. - if (notifyWhenNoCallbacksRunning == null) - { - var t = new Task((object)null, TaskCreationOptions.RunContinuationsAsynchronously); - _notifyWhenNoCallbacksRunning = t; - return new ValueTask(t); - } - - // A previous CloseAsync call already hooked up a task. Just return it. - return new ValueTask((Task)notifyWhenNoCallbacksRunning); - } - } - - internal void Fire() - { - bool canceled = false; - - lock (TimerQueue.Instance) - { - canceled = _canceled; - if (!canceled) - _callbacksRunning++; - } - - if (canceled) - return; - - CallCallback(); - - bool shouldSignal = false; - using (LockHolder.Hold(TimerQueue.Instance.Lock)) - { - _callbacksRunning--; - if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null) - shouldSignal = true; - } - - if (shouldSignal) - SignalNoCallbacksRunning(); - } - - internal void CallCallback() - { - ContextCallback callback = s_callCallbackInContext; - if (callback == null) - s_callCallbackInContext = callback = new ContextCallback(CallCallbackInContext); - - // call directly if EC flow is suppressed - if (_executionContext == null) - { - _timerCallback(_state); - } - else - { - ExecutionContext.Run(_executionContext, callback, this); - } - } - - private static ContextCallback s_callCallbackInContext; - - private static void CallCallbackInContext(object state) - { - TimerQueueTimer t = (TimerQueueTimer)state; - t._timerCallback(t._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. - // - internal sealed class TimerHolder - { - internal TimerQueueTimer m_timer; - - public TimerHolder(TimerQueueTimer timer) - { - m_timer = timer; - } - - ~TimerHolder() - { - m_timer.Close(); - } - - public void Close() - { - m_timer.Close(); - GC.SuppressFinalize(this); - } - - public bool Close(WaitHandle notifyObject) - { - bool result = m_timer.Close(notifyObject); - GC.SuppressFinalize(this); - return result; - } - - public ValueTask CloseAsync() - { - ValueTask result = m_timer.CloseAsync(); - GC.SuppressFinalize(this); - return result; - } - } - - public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable - { - private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe; - - private TimerHolder _timer; - - public Timer(TimerCallback callback, - object state, - int dueTime, - int period) : - this(callback, state, dueTime, period, flowExecutionContext: true) - { - } - - internal Timer(TimerCallback callback, - object state, - int dueTime, - int period, - bool flowExecutionContext) - { - if (dueTime < -1) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (period < -1) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - - TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext); - } - - public Timer(TimerCallback callback, - object state, - TimeSpan dueTime, - TimeSpan period) - { - long dueTm = (long)dueTime.TotalMilliseconds; - if (dueTm < -1) - throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTm > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge); - - long periodTm = (long)period.TotalMilliseconds; - if (periodTm < -1) - throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (periodTm > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge); - - TimerSetup(callback, state, (uint)dueTm, (uint)periodTm); - } - - [CLSCompliant(false)] - public Timer(TimerCallback callback, - object state, - uint dueTime, - uint period) - { - TimerSetup(callback, state, dueTime, period); - } - - public Timer(TimerCallback callback, - object state, - long dueTime, - long period) - { - if (dueTime < -1) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (period < -1) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTime > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); - if (period > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); - TimerSetup(callback, state, (uint)dueTime, (uint)period); - } - - 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). - - TimerSetup(callback, this, (uint)dueTime, (uint)period); - } - - private void TimerSetup(TimerCallback callback, - object state, - uint dueTime, - uint period, - bool flowExecutionContext = true) - { - if (callback == null) - throw new ArgumentNullException(nameof(TimerCallback)); - - _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext)); - } - - public bool Change(int dueTime, int period) - { - if (dueTime < -1) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (period < -1) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - - return _timer.m_timer.Change((uint)dueTime, (uint)period); - } - - public bool Change(TimeSpan dueTime, TimeSpan period) - { - return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds); - } - - [CLSCompliant(false)] - public bool Change(uint dueTime, uint period) - { - return _timer.m_timer.Change(dueTime, period); - } - - public bool Change(long dueTime, long period) - { - if (dueTime < -1) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (period < -1) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTime > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); - if (period > MAX_SUPPORTED_TIMEOUT) - throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); - - return _timer.m_timer.Change((uint)dueTime, (uint)period); - } - - public bool Dispose(WaitHandle notifyObject) - { - if (notifyObject == null) - throw new ArgumentNullException(nameof(notifyObject)); - - return _timer.Close(notifyObject); - } - - public void Dispose() - { - _timer.Close(); - } - - public ValueTask DisposeAsync() - { - return _timer.CloseAsync(); - } - } -} From 3fa2aea0f7e8e25b543aea40388a23804618135b Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sun, 27 Jan 2019 11:48:53 -0800 Subject: [PATCH 4/4] Keep CoreCLR-specific temporary hack under ifdef --- .../shared/System/Threading/Timer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs index fa4f56b5a3c..9e1f929fc07 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs @@ -472,8 +472,12 @@ internal bool Change(uint dueTime, uint period) } else { - // Don't emit this event during EventPipeController. This avoids initializing FrameworkEventSource during start-up which is expensive relative to the rest of start-up. - if (!EventPipeController.Initializing && FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + if ( +#if CORECLR + // Don't emit this event during EventPipeController. This avoids initializing FrameworkEventSource during start-up which is expensive relative to the rest of start-up. + !EventPipeController.Initializing && +#endif + FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true, (int)dueTime, (int)period); success = _associatedTimerQueue.UpdateTimer(this, dueTime, period);