fix: Fixes critical bug in TickCount calculation. (#2151)

### Summary

On some operating systems (like hosted Linux VMs), the `TimeStamp.GetTimeStamp()` CPU tick count will wrap around. This is generally not an issue, except for legacy reasons the TickCount is returned in milliseconds instead of ticks. This means when the values wrap around, they are already divided by the CPU Frequency (usually 1million) and then converted to milliseconds. That means the delta between the tick count before and after wrapping is off by a magnitude of (Frequency / 1000).

Example:

TimeStamp A = 9223372036654775807
Some time has passed:
TimeStamp B = -9223372036654775809

The raw delta is 400_000_000 (400ms) when you do `unchecked(A - B)`.
If we do the calculation AFTER converting it to milliseconds, then:

TickCount A = 9223372036654
TickCount B = -9223372036654

The raw delta is -18446744073308 instead of 400_000_000.

To fix this the calculation was changed so `long` -> `ulong`, then divided, then converted back to `long`, effectively bypassing wrap-around issue.

The new TickCount values in our example become:

TickCount A = 9223372037054
TickCount B = 9223372036654

The delta is 400 (in milliseconds). 🎉
This commit is contained in:
Kamron Batman 2025-04-09 16:46:53 -07:00 committed by GitHub
parent 0a4579f298
commit fdf8c5cf23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -346,8 +346,20 @@ public static class Core
}
}
private static readonly bool UseFastTimestampMath = Stopwatch.Frequency % 1000 == 0;
private static readonly ulong FrequencyInMilliseconds = (ulong)Stopwatch.Frequency / 1000;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long GetTimestamp() => 1000L * Stopwatch.GetTimestamp() / Stopwatch.Frequency;
public static long GetTimestamp()
{
if (UseFastTimestampMath)
{
return (long)((ulong)Stopwatch.GetTimestamp() / FrequencyInMilliseconds);
}
// Fast calculation will be lossy, fallback to slower but accurate calculation
return (long)((UInt128)Stopwatch.GetTimestamp() * 1000 / (ulong)Stopwatch.Frequency);
}
public static void Setup(Assembly applicationAssembly, Process process)
{