#36049 streams: avoid termination on buffered write failure

full analysis

https://github.com/bitcoin/bitcoin/pull/36049 · l0rinc · +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

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.

Data

dossier JSON · extract JSON · model openrouter/google/gemini-3.8-flash, generated 2026-09-17T21:47, confidence high, input hash c220a7ad70449370