Bug 96
Failed startup does not exit while a mining client is waiting
- Status
- Open
- Symptom
- Hang; worst effect seen: hang
- Where seen
- Bitcoin Core functional tests
- Trigger
- Node startup
- First reported
- Reported privately in an independent security review, 2026-09-27
- Fixed
- Not yet
- In released binaries
- Yes (v30.0 onward)
- Defect ever on master
- Yes
Summary
If bitcoind or bitcoin-node fails to start while an IPC mining client is waiting inside the node, for example in makeMining for the chain to load, the node logs "Shutdown done" and then stays running until it is killed. With -daemon, the stuck process also keeps the data directory locked, so a corrected restart fails. The cause is that the startup-failure path never raises the shutdown signal that every mining wait checks. It was reported privately in an independent security review, with a functional test. No fix is merged yet.
What happened
Nothing has been seen in a deployment. An independent security review reported it privately on 2026-09-27. Its functional test replaces peers.dat with a named pipe, so startup blocks after the IPC socket is listening, waits until the node is executing the client's makeMining request, and then writes a corrupt peers.dat so startup fails. Without the fix the node logs "Shutdown done" and does not exit. The report also describes earlier runs, not repeated on its final branch: the same hang after a later -bind failure with a client waiting in waitTipChanged, and a -daemon run in which the parent had already reported a successful start while the failed child kept the data directory lock.
Root cause
main in src/bitcoind.cpp, which bitcoin-node also uses, ends like this:
if (!AppInit(node) || !Assert(node.shutdown_signal)->wait()) {
node.exit_status = EXIT_FAILURE;
}
Interrupt(node);
Shutdown(node);
When AppInit fails, nothing raises the shutdown signal. The IPC mining waits treat that signal as their only shutdown condition. Since bitcoin#34661, makeMining waits for the chainstate to load or for the signal. waitTipChanged, waitNext and the stages of createNewBlock check the chain manager's interrupt flag, which is the same signal. Interrupt() wakes these waiters, but each finds nothing changed and waits again.
Shutdown() then closes the IPC connections, but the worker thread is still inside node code. The parked request keeps the event loop alive, and the IPC teardown that runs after "Shutdown done" waits forever for the event loop thread to finish.
This is the mirror image of an earlier bug where Ctrl-C did not stop a node with a waiting mining client. There the signal was raised but nothing woke the waiters; here the waiters are woken but the signal is never raised. The GUI does not have this problem, because its startup failure path calls startShutdown(), which raises the signal.
Fix
No fix merged yet. The fix proposed with the report raises the shutdown signal when AppInit fails, before Interrupt() runs. The woken waits then see it and return, makeMining returns no interface, and the process exits with status 1. Because every mining wait checks the same signal, this one change covers all of them.
Next steps
- Open a Bitcoin Core PR with the
bitcoind.cppchange and the named pipe test. - Consider raising the signal inside
Interrupt()itself, so no shutdown path can skip it. - Land it before any change that makes shutdown wait for running IPC calls, such as bitcoin#35932. Otherwise the hang moves into
Shutdown()itself.
Timeline
- 2024-09-09: bitcoin#30509 ("multiprocess: Add -ipcbind option to bitcoin-node") merged; IPC clients can connect while the node is still starting.
- 2025-04-14: bitcoin#31785 merged;
waitTipChangedwaits for a tip and returns only on the shutdown signal, so a client waiting there when a later startup step fails can already hang. - 2026-02-27: bitcoin#34661 merged;
makeMiningnow waits during startup, which makes the hang easy to reach. - 2026-09-27: reported privately in an independent security review.
- Releases: v30.0 and later have the bug (with
waitTipChanged); from v31.0 also withmakeMining.
Lessons
What would have prevented this bug, found it sooner, or made it easier to debug. The patterns page groups these lessons across all bugs.
Pattern Locks, waits and wakeups: Wakeup without interrupt
- Prevented by a design rule
- Earliest detector startup test (did not exist then)
- Diagnosis moderate
- Fix design easy
- Debugging aids logging, symbolized stack traces
- Rust no
Would Rust have prevented it? No. Whether a shutdown path sets the flag that waiters check is control flow; a Rust program with a Condvar and an AtomicBool can skip the store on one path just the same. More on Rust
Could it have been prevented?
Yes, with one rule: every shutdown path goes through the same "request shutdown" step
that sets the flag waiters check, and only then wakes them. Bitcoin Core had three
paths: the stop RPC, signals (Ctrl-C, SIGTERM), and a failed start. The first two
were brought together by bitcoin#33511,
which moved the wake-up into Interrupt(). The failed-start path in bitcoind.cpp calls
Interrupt() without ever raising the signal. Raising it inside Interrupt() would make
the paths unable to differ.
Could it have been found sooner?
Yes, by a startup-failure test with an IPC client waiting. The functional tests cover
failed starts (feature_init.py) and early IPC connections (since
bitcoin#34661), but not both together.
The reporter's test needed a named pipe in place of peers.dat to hold startup at the
right moment, which is the main difficulty.
What made it hard to debug?
The process looks finished: it logs "Shutdown done", and only a thread dump shows the worker parked in the wait and the main thread joining the event loop. That trace was already known from bitcoin#33511, which makes this one quicker to recognize. In daemon mode the visible symptom is a later "cannot obtain a lock" error from a fresh start, far from the cause.
Lesson
Waking waiters is only half of a shutdown; the condition they wait for must also become true on every exit path, including a failed start, or the wake-up does nothing.
Classification
The survey's classification of this bug. The explorer charts these fields across all bugs; the method page explains them.
| Field | Value |
|---|---|
| Symptom | hang |
| Worst effect observed | hang |
| Seen in | functional-test |
| Scope | production |
| Trigger | Node startup (node-startup) |
| Phase | startup |
| Side | server |
| Component | bitcoin-init |
| Root cause kind | missing-cancellation |
| Fix kind | none |
| Detection | review |
| Needs a peer that is not libmultiprocess C++ | no |
| Needed a sanitizer to see | no |
| Defect ever on master | yes |
| Released | binary |
| Confidence | high |