Repository navigation
rest: make StdioSession work in a forked child - #237
Merged
ThomasWaldmann merged 1 commit intoOct 29, 2026
Merged
Conversation
A daemonized "borg mount" opens the store, then forks. The stderr drain thread does not survive fork(), so in the daemon: - nothing drains the server's stderr any more: its messages get lost and the server blocks once the pipe buffer is full. - the old thread was blocked reading stderr at fork time, holding the buffered reader's lock, so closing that reader waited forever (borgbackup/borg#10566). When used in a different process than the one that started the stderr thread, read a dup of the stderr fd with a new reader and start a new drain thread. Close the old reader's unbuffered raw file, which takes no lock and also makes the old reader count as closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
stdio-close-after-fork
branch
from
October 29, 2026 21:29
8624db7 to
2477cb1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes borgbackup/borg#10566: a daemonized
borg mount(2.0.0b24+, rest/ssh stdio repo) never exits afterborg umount.Cause
borg mountopens the store and then forks to daemonize. TheStdioSessionstderr drain thread does not survivefork(), so in the daemon:read()on the server's stderr, holding theBufferedReader's internal lock.process.stderr.close()waits for that lock forever (the reported hang).Remote: ...debug lines, no stderr tail in error messages), and once the pipe buffer (64 KiB on Linux) is full, the server blocks writing stderr and stops answering requests.Fix
The session records the pid that started the drain thread. When
request()orclose()runs in a different process (a forked child),_check_fork():os.dup()of the stderr fd in a new reader (fresh, unheld lock) and starts a new drain thread on it,close()then uses the normal path. Same-process behaviour is unchanged (oneos.getpid()per request). This is done lazily rather than viaos.register_at_fork(), because at-fork hooks also run inPopen(..., preexec_fn=...)children before exec, where starting a thread is unsafe.Remaining limitation (as before): the server is a child of the original process, not of the daemon, so
wait()in the daemon cannot see its exit code.Tests
test_stdio_session_works_in_forked_childmimics daemonizing: a child opens the session and forks again, the intermediate parent exits, and the grandchild does a request where the server writes ~200 KB to stderr, then closes. It passes with this fix and hangs (killed after 10 s, test fails) both without any fix and with only a non-blocking close.borg mountoverssh://: without the fix the daemon stays stuck instderr.close()afterborg umount; with it, it exits ~2 s afterumount, no leftover processes, repo lock released. Withborg serve --debug, the daemon's server stderr pipe holds 0 unread bytes with the fix vs. stale unread output without it.🤖 Generated with Claude Code