// 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.
+ // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
//
internal class TimerQueue
{
- #region singleton pattern implementation
+ #region Shared TimerQueue instances
- // The one-and-only TimerQueue for the AppDomain.
- private static TimerQueue s_queue = new TimerQueue();
+ public static TimerQueue[] Instances { get; } = CreateTimerQueues();
- public static TimerQueue Instance
+ private TimerQueue(int id)
{
- get { return s_queue; }
+ m_id = id;
}
- private TimerQueue()
+ private static TimerQueue[] CreateTimerQueues()
{
- // empty private constructor to ensure we remain a singleton.
+ var queues = new TimerQueue[Environment.ProcessorCount];
+ for (int i = 0; i < queues.Length; i++)
+ {
+ queues[i] = new TimerQueue(i);
+ }
+ return queues;
}
#endregion
}
}
+ private readonly int m_id; // TimerQueues[m_id] == this
private AppDomainTimerSafeHandle m_appDomainTimer;
private bool m_isAppDomainTimerScheduled;
if (m_appDomainTimer == null || m_appDomainTimer.IsInvalid)
{
Debug.Assert(!m_isAppDomainTimerScheduled);
+ Debug.Assert(m_id >= 0 && m_id < Instances.Length && this == Instances[m_id]);
- m_appDomainTimer = CreateAppDomainTimer(actualDuration);
+ m_appDomainTimer = CreateAppDomainTimer(actualDuration, m_id);
if (!m_appDomainTimer.IsInvalid)
{
m_isAppDomainTimerScheduled = true;
}
//
- // The VM calls this when the native timer fires.
+ // The VM calls this when a native timer fires.
//
- internal static void AppDomainTimerCallback()
+ internal static void AppDomainTimerCallback(int id)
{
- Instance.FireNextTimers();
+ Debug.Assert(id >= 0 && id < Instances.Length && Instances[id].m_id == id);
+ Instances[id].FireNextTimers();
}
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
- private static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime);
+ private static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime, int id);
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
//
// 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()
{
internal sealed class TimerQueueTimer
{
//
+ // The associated timer queue.
+ //
+ private readonly TimerQueue m_associatedTimerQueue;
+
+ //
// All fields of this class are protected by a lock on TimerQueue.Instance.
//
// The first four fields are maintained by TimerQueue itself.
m_dueTime = Timeout.UnsignedInfinite;
m_period = Timeout.UnsignedInfinite;
m_executionContext = ExecutionContext.Capture();
+ m_associatedTimerQueue = TimerQueue.Instances[Environment.CurrentExecutionId % TimerQueue.Instances.Length];
//
// After the following statement, the timer may fire. No more manipulation of timer state outside of
Change(dueTime, period);
}
-
internal bool Change(uint dueTime, uint period)
{
bool success;
- lock (TimerQueue.Instance)
+ lock (m_associatedTimerQueue)
{
if (m_canceled)
throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
if (dueTime == Timeout.UnsignedInfinite)
{
- TimerQueue.Instance.DeleteTimer(this);
+ m_associatedTimerQueue.DeleteTimer(this);
success = true;
}
else
if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true);
- success = TimerQueue.Instance.UpdateTimer(this, dueTime, period);
+ success = m_associatedTimerQueue.UpdateTimer(this, dueTime, period);
}
}
}
public void Close()
{
- lock (TimerQueue.Instance)
+ lock (m_associatedTimerQueue)
{
// prevent ThreadAbort while updating state
try { }
if (!m_canceled)
{
m_canceled = true;
- TimerQueue.Instance.DeleteTimer(this);
+ m_associatedTimerQueue.DeleteTimer(this);
}
}
}
bool success;
bool shouldSignal = false;
- lock (TimerQueue.Instance)
+ lock (m_associatedTimerQueue)
{
// prevent ThreadAbort while updating state
try { }
{
m_canceled = true;
m_notifyWhenNoCallbacksRunning = toSignal;
- TimerQueue.Instance.DeleteTimer(this);
+ m_associatedTimerQueue.DeleteTimer(this);
if (m_callbacksRunning == 0)
shouldSignal = true;
{
bool canceled = false;
- lock (TimerQueue.Instance)
+ lock (m_associatedTimerQueue)
{
// prevent ThreadAbort while updating state
try { }
CallCallback();
bool shouldSignal = false;
- lock (TimerQueue.Instance)
+ lock (m_associatedTimerQueue)
{
// prevent ThreadAbort while updating state
try { }
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
-
- // If there were any DelegateInfos waiting to be released, they'll get flushed now
- ThreadpoolMgr::FlushQueueOfTimerInfos();
DelegateInfoHolder delegateInfo = (DelegateInfo*) ThreadpoolMgr::GetRecycledMemory(ThreadpoolMgr::MEMTYPE_DelegateInfo);
LogCall(pMeth,"AppDomainTimerCallback");
#endif
- MethodDescCallSite(METHOD__TIMER_QUEUE__APPDOMAIN_TIMER_CALLBACK).Call(NULL);
+ ThreadpoolMgr::TimerInfoContext* pTimerInfoContext = (ThreadpoolMgr::TimerInfoContext*)ptr;
+ ARG_SLOT args[] = { PtrToArgSlot(pTimerInfoContext->TimerId) };
+ MethodDescCallSite(METHOD__TIMER_QUEUE__APPDOMAIN_TIMER_CALLBACK).Call(args);
}
-VOID WINAPI AppDomainTimerCallback(PVOID delegateInfo, BOOLEAN timerOrWaitFired)
+VOID WINAPI AppDomainTimerCallback(PVOID callbackState, BOOLEAN timerOrWaitFired)
{
Thread* pThread = GetThread();
if (pThread == NULL)
MODE_ANY;
GC_TRIGGERS;
SO_INTOLERANT;
-
- PRECONDITION(CheckPointer(delegateInfo));
}
CONTRACTL_END;
GCX_COOP();
- ManagedThreadBase::ThreadPool(((DelegateInfo*)delegateInfo)->m_appDomainId, AppDomainTimerCallback_Worker, NULL);
+ ThreadpoolMgr::TimerInfoContext* pTimerInfoContext = (ThreadpoolMgr::TimerInfoContext*)callbackState;
+ ManagedThreadBase::ThreadPool(pTimerInfoContext->AppDomainId, AppDomainTimerCallback_Worker, pTimerInfoContext);
// We should have released all locks.
_ASSERTE(g_fEEShutDown || pThread->m_dwLockCount == 0 || pThread->m_fRudeAborted);
}
-HANDLE QCALLTYPE AppDomainTimerNative::CreateAppDomainTimer(INT32 dueTime)
+HANDLE QCALLTYPE AppDomainTimerNative::CreateAppDomainTimer(INT32 dueTime, INT32 timerId)
{
QCALL_CONTRACT;
BEGIN_QCALL;
_ASSERTE(dueTime >= 0);
+ _ASSERTE(timerId >= 0);
AppDomain* pAppDomain = GetThread()->GetDomain();
ADID adid = pAppDomain->GetId();
- DelegateInfoHolder delegateInfo = DelegateInfo::MakeDelegateInfo(
- pAppDomain,
- NULL,
- NULL,
- NULL);
+ ThreadpoolMgr::TimerInfoContext* timerContext = new (nothrow) ThreadpoolMgr::TimerInfoContext();
+ if (timerContext == NULL)
+ {
+ COMPlusThrowOM();
+ }
+
+ timerContext->AppDomainId = adid;
+ timerContext->TimerId = timerId;
BOOL res = ThreadpoolMgr::CreateTimerQueueTimer(
&hTimer,
(WAITORTIMERCALLBACK)AppDomainTimerCallback,
- (PVOID)delegateInfo,
+ (PVOID)timerContext,
(ULONG)dueTime,
(ULONG)-1 /* this timer doesn't repeat */,
0 /* no flags */);
if (!res)
{
+ delete timerContext;
+
if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
COMPlusThrow(kNotSupportedException);
else
COMPlusThrowWin32();
}
- else
- {
- delegateInfo.SuppressRelease();
- }
END_QCALL;
return hTimer;