#36049 streams: avoid termination on buffered write failure
https://github.com/bitcoin/bitcoin/pull/36049 · · +20/-3 in 3 files, 3 commits · labels: none
Goal
- Prevent abrupt node crashes when encountering storage errors during block writing
- Allow standard storage error handling and graceful shutdown when disks run out of space
This pull request ensures that `BufferedWriter::flush()` clears its pending buffer counter prior to invoking the underlying stream write. It also adds explicit `flush()` calls before block and undo storage writers go out of scope, preventing write errors from throwing inside `~BufferedWriter()` and triggering `std::terminate`.
Problem: When writing blocks or undo data to a filesystem that has run out of space, write failures throw an exception inside `BufferedWriter`'s destructor. Because destructors are implicitly non-throwing, this triggers `std::terminate` instead of allowing the node to report the storage failure and shut down gracefully.
Category: Utilities (logging, arguments, libraries) (#24 of 66)
P3 · bug fix
- P3 because it fixes an exception safety bug in buffered streams
- Prevents a destructor from triggering an immediate crash during exception unwinding
P3 because it fixes an exception-safety bug in `BufferedWriter::flush()` where failing to clear `m_buf_pos` before writing caused duplicate writes and uncatchable exceptions during stack unwinding. As author notes: 'Calling flush() before destruction is insufficient on its own because a failed flush leaves the same bytes pending, so the destructor retries the write while the original exception unwinds.'
Membership: Modifies `BufferedWriter` in `src/streams.h` to make buffered stream flushing exception-safe.
Factors: security/stability 2, bug 1, performance 0, user value 1, leverage 0
Category: Validation (#30 of 48)
P3 · bug fix
- P3 because it routes block write errors to standard storage failure handling rather than hard aborts
- Applies only to failure recovery paths when storage runs out of space or fails
P3 because it prevents unhandled `std::terminate` process aborts when writing blocks and undo data under full-disk conditions. Author notes that with explicit flushing, 'it reports Failed to write genesis block and exits 1 instead of terminating the process.'
Membership: Changes block and block undo file storage error handling in `src/node/blockstorage.cpp`.
Factors: security/stability 2, bug 1, performance 0, user value 1, leverage 0
Reviewability: Ready
- Ready for review as the patch is small and CI is passing
The patch is small, self-contained, clean, and CI is passing.
Author status: Silent for 27 days since addressing initial feedback on 2026-08-20
Open concerns:
- Whether this class of exception-during-destruction issues should be addressed holistically across the codebase rather than piecemeal
Resolved concerns:
- Clarified why catching the exception is preferable to an immediate crash on storage write failure
Agreement: Strong
- General agreement that the bug exists and should be addressed
- Concept approval after independently hitting the identical bug (w0xlt)
- Inquired whether terminating was acceptable for storage errors, which author resolved (andrewtoth)
Strong concept support from w0xlt; maflcko raised general interest in wholesale solutions for throwing destructors.
w0xlt gave a substantive Concept ACK after running into the exact same bug, andrewtoth's question on terminate versus error handling was answered, and maflcko's comment was general musing rather than a blocking objection.
- andrewtoth: 'Can you expand on why this is a problem? If there is a storage error, don't we have to crash anyways?'
- l0rinc: 'Handling the exception lets the node report the storage error and run its normal shutdown cleanup.'
- maflcko: 'It would be good to find a code pattern that avoids this class of problem wholesale.'
- w0xlt: 'Concept ACK. I ran into the same bug while working in this area and implemented an alternative fix before finding this PR.'
Objections: none enumerated.
Support:
- w0xlt: Encountered the same bug when working in the area and confirmed the need for a fix
Participants: andrewtoth (question), maflcko (neutral), w0xlt (support)
State derived from the lists: substantive support, no open objection (w0xlt)
Review verdicts (DrahtBot): 0
- Concept ACK: w0xlt
Files
14 lines under test/bench/ci.
- src/test/streams_tests.cpp +14/-0
- src/streams.h +4/-2
- src/node/blockstorage.cpp +2/-1
Card
This PR fixes an exception-safety bug in `BufferedWriter` where write failures caused unhandled exceptions in the destructor and triggered `std::terminate`. By setting the pending buffer count to zero before writing and explicitly calling `flush()` in `BlockManager::WriteBlock` and `WriteBlockUndo`, storage failures when the disk fills up result in clean shutdown handling instead of an immediate SIGABRT. The PR has a Concept ACK from a contributor who ran into the same issue, and review is ready.