A while ago, I got reminded of a few occurrences of Delphi apps hanging after a little less than 2 months of being active.
It does not happen that often, as usually Patch Tuesday requires a reboot, but sometimes it doesn’t, or ops forgot to patch the affected machine.
The common thing for these hanging apps was that they all used the System.Threading
unit (introduced in Delphi XE7). That unit relied on [Wayback] TThread.GetTickcount
(added in Delphi XE3 in the System.Classes
unit) which uses a 32-bit unsigned [Archive.is] cardinal
value as it relied on the Windows API [Wayback] GetTickCount function which in turn has this remark:
The elapsed time is stored as a
DWORD
value. Therefore, the time will wrap around to zero if the system is run continuously for 49.7 days. To avoid this problem, use the GetTickCount64 function. Otherwise, check for an overflow condition when comparing times.
The reminder was in [Wayback] Delphi 11 Windows XP compatibility tweak – RTL and Delphi Object Pascal – Delphi-PRAXiS [en] (Thanks [Wayback] Alexander Elagin), as Delphi 11 finally switched away from GetTickCount
to GetTickCount64:
using
GetTickCount64
in fact is a must for their code in theThreading
unit which internally uses the milliseconds counter. As you know, the counter overflows every 49 or so days and some threads in your server application just hang waiting until the conditionTThread.GetTickCount > FLastSuspendTick.Value + TThreadPool.SuspendInterval
is fulfilled…
The multimedia Windows API function [Wayback] timeGetTime,
though giving higher resolution, was no use: it takes longer to execute and is still limited to a DWORD value for Ticks:
Note that the value returned by the
timeGetTime
function is aDWORD
value. The return value wraps around to 0 every 2^32 milliseconds, which is about 49.71 days. This can cause problems in code that directly uses thetimeGetTime
return value in computations, particularly where the value is used to control code execution. You should always use the difference between twotimeGetTime
return values in computations.
Delphi 11 switched to [Wayback] GetTickCount64, which is unavailable in Windows XP nor Windows Server 2003, and does not overflow every 49.7 days.
So basically, be sure to restart any Delphi app relying on the System.Threading
unit no later than 49 days of use, or recompile with Delphi 11 and up. Or better: wait for the first Delphi 11 update (it should have appeared by now), as that will likely flesh out quite a few issues not grabbed during the field test cycle.
Note that other environments can also suffer from similar overflows:
- [Wayback] Environment.TickCount Property (System) | Microsoft Docs (via [Wayback] c# – How Environment.TickCount overflows after 25 days (or 48-49 days) – Stack Overflow, thanks [Wayback] Llama):
Because the value of the TickCount property value is a 32-bit signed integer, if the system runs continuously, TickCount will increment from zero to Int32.MaxValue for approximately 24.9 days, then jump to Int32.MinValue, which is a negative number, then increment back to zero during the next 24.9 days. You can work around this issue by calling the Windows GetTickCount function, which resets to zero after approximately 49.7 days, or by calling the GetTickCount64 function.
- [Wayback] How many days can I store in 4 bytes with millisecond precision? – Stack Overflow (thanks [Wayback] Marius Bancila):
4 bytes = 2^32 = 4294967296
1 day = 24 * 60 * 60 * 1000 miliseconds = 86400000
4294967296 / 86400000 = 49.71026962962962962962962962963
or about 49 days 17 hours 2 minutes and 47 seconds
- [Wayback] c++ – Cross platform millisecond timer lasting more than 49 days? – Stack Overflow: use a 64-bit timer value
–jeroen