Fix timer callback time drift (dotnet/coreclr#11220)
authorKoundinya Veluri <kouvel@microsoft.com>
Wed, 26 Apr 2017 23:08:53 +0000 (16:08 -0700)
committerGitHub <noreply@github.com>
Wed, 26 Apr 2017 23:08:53 +0000 (16:08 -0700)
Fixes dotnet/coreclr#6408:
- The change to S.T.Timer is the actual fix, but fixed the timer firing on the thread pool timer thread similarly as well

Commit migrated from https://github.com/dotnet/coreclr/commit/c55f023f542e63e93a300752432de7bcc4104b3b

src/coreclr/src/mscorlib/src/System/Threading/Timer.cs
src/coreclr/src/vm/win32threadpool.cpp

index 960f815..a5c7945 100644 (file)
@@ -265,7 +265,19 @@ namespace System.Threading
                             if (timer.m_period != Timeout.UnsignedInfinite)
                             {
                                 timer.m_startTicks = nowTicks;
-                                timer.m_dueTime = timer.m_period;
+                                uint elapsedForNextDueTime = elapsed - timer.m_dueTime;
+                                if (elapsedForNextDueTime < timer.m_period)
+                                {
+                                    // Discount the extra amount of time that has elapsed since the previous firing time to
+                                    // prevent 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 spinning without a delay.
+                                    timer.m_dueTime = 1;
+                                }
 
                                 //
                                 // This is a repeating timer; schedule it to run again.
index bc84762..a79656e 100644 (file)
@@ -4755,15 +4755,30 @@ DWORD ThreadpoolMgr::FireTimers()
                                   timerInfo,
                                   QUEUE_ONLY /* TimerInfo take care of deleting*/);
 
-                timerInfo->FiringTime = currentTime+timerInfo->Period;
+                if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1)
+                {
+                    ULONG nextFiringTime = timerInfo->FiringTime + timerInfo->Period;
+                    DWORD firingInterval;
+                    if (TimeExpired(timerInfo->FiringTime, currentTime, nextFiringTime))
+                    {
+                        // 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 spinning without a delay.
+                        timerInfo->FiringTime = currentTime + 1;
+                        firingInterval = 1;
+                    }
+                    else
+                    {
+                        timerInfo->FiringTime = nextFiringTime;
+                        firingInterval = TimeInterval(nextFiringTime, currentTime);
+                    }
 
-                if ((timerInfo->Period != 0) && (timerInfo->Period != (ULONG) -1) && (nextFiringInterval > timerInfo->Period))
-                    nextFiringInterval = timerInfo->Period;
+                    if (firingInterval < nextFiringInterval)
+                        nextFiringInterval = firingInterval;
+                }
             }
-
             else
             {
-                DWORD firingInterval = TimeInterval(timerInfo->FiringTime,currentTime);
+                DWORD firingInterval = TimeInterval(timerInfo->FiringTime, currentTime);
                 if (firingInterval < nextFiringInterval)
                     nextFiringInterval = firingInterval;
             }