Severity: Info
Location: src/Servy.Infrastructure/Data/DapperExecutor.cs:73-75
Code:
// Use SpinWait to pause the current thread.
// Unlike Thread.Sleep, this avoids parking the thread and starving the ThreadPool for short durations.
SpinWait.SpinUntil(() => false, delay);
Explanation:
SpinWait.SpinUntil is designed to busy-spin while a predicate transitions to true, with delay as a timeout. Here the predicate is () => false, which can never become true, so SpinUntil unconditionally spins for the full delay duration every time. The effective behavior is identical to Thread.Sleep(delay), but with three drawbacks:
- Misleading intent. A reader seeing
SpinUntil expects a real condition — a future maintainer will waste time trying to figure out what it's waiting for.
- SpinWait isn't as CPU-cheap as the comment suggests. The .NET implementation of
SpinWait yields to the OS scheduler after ~10 iterations (via Thread.Sleep(0), then Thread.Sleep(1) for longer waits), so for the SQLite retry delays here (backoff = SyncInitialDelayMs * Math.Pow(2, i)) which quickly grow past the spin threshold, the call ends up parking the thread just like Thread.Sleep — contradicting the in-code justification.
- Hot-path cost. Inside a retry loop on a DB-busy path, this can burn measurable CPU for the first few milliseconds of each delay before the runtime bails out to a real sleep.
Suggested fix:
Use the idiomatic sync sleep — the behavior is the same and the intent is obvious:
If the original concern was genuinely "don't burn a ThreadPool thread", the correct pattern is to make the sync path rare (which the retry-with-backoff already does) rather than substituting a busy-spin. If the author wants to support short-delay spins explicitly:
if (delay < 5)
{
var sw = new SpinWait();
var deadline = Environment.TickCount + delay;
while (Environment.TickCount < deadline) sw.SpinOnce();
}
else
{
Thread.Sleep(delay);
}
…but this is almost never worth the complexity.
Distinct from #761 (RotatingStreamWriter sync rotation stalls) and #759 (Thread.Sleep retry logic) — those address different timeouts; this is specifically about the misleading SpinUntil idiom in the sync Dapper retry path.
Severity: Info
Location:
src/Servy.Infrastructure/Data/DapperExecutor.cs:73-75Code:
Explanation:
SpinWait.SpinUntilis designed to busy-spin while a predicate transitions to true, withdelayas a timeout. Here the predicate is() => false, which can never become true, soSpinUntilunconditionally spins for the fulldelayduration every time. The effective behavior is identical toThread.Sleep(delay), but with three drawbacks:SpinUntilexpects a real condition — a future maintainer will waste time trying to figure out what it's waiting for.SpinWaityields to the OS scheduler after ~10 iterations (viaThread.Sleep(0), thenThread.Sleep(1)for longer waits), so for the SQLite retry delays here (backoff = SyncInitialDelayMs * Math.Pow(2, i)) which quickly grow past the spin threshold, the call ends up parking the thread just likeThread.Sleep— contradicting the in-code justification.Suggested fix:
Use the idiomatic sync sleep — the behavior is the same and the intent is obvious:
If the original concern was genuinely "don't burn a ThreadPool thread", the correct pattern is to make the sync path rare (which the retry-with-backoff already does) rather than substituting a busy-spin. If the author wants to support short-delay spins explicitly:
…but this is almost never worth the complexity.
Distinct from #761 (RotatingStreamWriter sync rotation stalls) and #759 (Thread.Sleep retry logic) — those address different timeouts; this is specifically about the misleading
SpinUntilidiom in the sync Dapper retry path.