Description
In src/Servy.Core/Helpers/ProcessHelper.cs (lines 113–138):
private static DateTime _lastPruneTime = DateTime.MinValue;
private static void PruneDeadProcesses()
{
if (DateTime.UtcNow - _lastPruneTime < PruneInterval) return;
// ... prune logic ...
_lastPruneTime = DateTime.UtcNow;
}
_lastPruneTime is a static field accessed from multiple threads (via RefreshServiceInternal which runs parallel tasks). The check-then-act pattern is a TOCTOU race: multiple threads can pass the interval check simultaneously and all execute the prune loop.
Additionally, DateTime is a 64-bit struct — on x86, reads/writes are not atomic (they require two 32-bit operations), making torn reads possible.
Severity
Info — redundant concurrent prune executions; torn reads possible on x86.
Suggested fix
Use Interlocked.Exchange with a long (ticks) instead of DateTime:
private static long _lastPruneTicks = 0;
private static void PruneDeadProcesses()
{
long now = DateTime.UtcNow.Ticks;
long last = Interlocked.Read(ref _lastPruneTicks);
if (now - last < PruneInterval.Ticks) return;
if (Interlocked.CompareExchange(ref _lastPruneTicks, now, last) != last) return;
// ... prune logic (only one thread enters) ...
}
Description
In
src/Servy.Core/Helpers/ProcessHelper.cs(lines 113–138):_lastPruneTimeis a static field accessed from multiple threads (viaRefreshServiceInternalwhich runs parallel tasks). The check-then-act pattern is a TOCTOU race: multiple threads can pass the interval check simultaneously and all execute the prune loop.Additionally,
DateTimeis a 64-bit struct — on x86, reads/writes are not atomic (they require two 32-bit operations), making torn reads possible.Severity
Info — redundant concurrent prune executions; torn reads possible on x86.
Suggested fix
Use
Interlocked.Exchangewith along(ticks) instead ofDateTime: