Counterfactual
Use this to avoid patching symptoms. The output must separate the minimal correct algorithm from the current algorithm, then name the smallest intervention that moves current toward correct.
Output shape
Use exactly these sections:
- Goal: one sentence stating the invariant we need.
- Minimal algorithm: pseudocode or numbered steps for the ideal implementation. No library constraints yet.
- Current algorithm: what the code actually does today, including ownership, blocking points, and cleanup boundaries.
- Gap: the first point where current diverges from minimal.
- Smallest fix: one intervention. Preserve working invariants.
- Falsifier: the test or measurement that proves the fix wrong.
Rules
- Do not start with a proposed patch. Start with the invariant.
- Do not compare against a strawman. Read the code and name exact functions, fields, and blocking calls.
- Preserve constraints that are already correct. If a prior commit fixed a deadlock, do not revert it to fix a leak.
- Prefer ownership/cancellation changes over retries, sleeps, or cleanup after the fact.
- A good fix reduces the gap; a great fix removes an entire class of impossible states.
Checklist
- Where is ownership supposed to live?
- What wakes every blocking syscall?
- Who joins or intentionally detaches each thread?
- Which fd owner closes each fd?
- What happens if the happy-path event never occurs?
- What is the failure mode under load?
Example
Goal: Dropping a per-machine vhost-user server must not block the registry actor and must not leak fds when no VM ever connects.
Minimal algorithm: bind listener; spawn accept loop cancellable by a shutdown token; Drop fires token, wakes listener, signals workers, joins threads with a bound, and closes all owned fds.
Current algorithm: start() moves VhostUserDaemon into an accept
thread and calls daemon.start(&mut listener). Drop removes the socket
path, then tries to take the daemon from Arc<Mutex<Option<_>>>. If the
thread is blocked in accept(), the option is None, so cleanup is
skipped.
Gap: unlinking a Unix socket path does not wake a blocked accept(),
so the daemon owner never returns to Drop.
Smallest fix: keep Drop nonblocking, but spawn a bounded reaper that
self-connects to wake accept(), waits for daemon handoff, signals
workers, and joins the protocol thread off the actor thread.
Falsifier: create and drop N servers without clients, then assert the fd delta over a control arm stays below the regression threshold. If fd count grows linearly with N, the fix is wrong.