Skip to content

[Code Quality] DapperExecutor.cs — SpinWait.SpinUntil(() => false, delay) misuses SpinWait as Thread.Sleep #779

@Christophe-Rogiers

Description

@Christophe-Rogiers

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:

  1. 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.
  2. 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.
  3. 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:

Thread.Sleep(delay);

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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions