Repository navigation
fix(cli): exit watch and files once their shutdown flush returns - #209
Merged
Merged
Conversation
A headless `torlnk watch` under a supervisor flushed queue.json, seeds.json and history.json the instant SIGTERM arrived and then stayed alive for hours until kill -9. seed and serve exit explicitly once their promise settles (#183, 918d3f6); watch and files only had the error handler, so they relied on the event loop draining on its own, which means waiting on webtorrent's async teardown. One peer connection webtorrent has lost track of (the #205 orphan) is enough to keep that from ever finishing: on 2.8.5 it is a ref'd UDP socket of its own, on 3.x it keeps the shared uTP server socket from closing. Either way the loop never empties, and the same hang makes `torlnk update` report a daemon as still shutting down and never relaunch it. Reproduced on both webtorrent lines by injecting a single orphan into the real process: watch stayed alive 20 s after SIGTERM and needed SIGKILL while serve and seed exited in 255 ms. With this change watch exits in 255 ms with status 0, its state files still rewritten at the signal; files exits the same way with a transfer in flight. The daemon modules keep resolving from their signal handler after the synchronous flush, which is the contract that keeps them unit-testable; the CLI exits right there and lets the OS reclaim the sockets, the bargain engine.destroy() and the TUI's forceExit already make. serve was reported too but has had this exit since 1.8.0 and could not be made to hang. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hei7LB3kzJELvzVCTmVrQa
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
ThreatCrush Security Scan0 finding(s) No findings. |
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.
What and why
Fixes #207. Follows #208 (rebased onto it after the squash merge).
A headless
torlnk watchunder a supervisor flushesqueue.json/seeds.json/history.jsonthe instant SIGTERM arrives, then stays alive for hours untilkill -9. The reporter read the main point correctly:src/index.tsxexitsseedandserveexplicitly once their shutdown promise settles (#183 and918d3f6), but thewatchandfilesbranches only had the error handler. They relied on the event loop draining on its own, which means waiting on webtorrent's async teardown.One peer connection webtorrent has lost track of is enough to keep that teardown from ever finishing. That is the #205 orphan (
_draindialing a peer thatremovePeeralready destroyed): on webtorrent 2.8.5 it is a ref'd UDP socket of its own, on 3.x it keeps the shared uTP server socket from closing. Either way the loop never empties, so the webtorrent 3 upgrade in #208 does not fix this on its own. The same hang makestorlnk updatereport a daemon as "still shutting down; skipped" and never relaunch it.Reproduced by injecting exactly one such orphan into the real
torlnkprocess (a--importpreload that queues a peer, destroys it while queued, and lets_draindial it against a local uTP peer that accepts and never speaks), then sending SIGTERM:Without the orphan every mode exits, which is why this only shows on real swarms. With an empty queue no webtorrent client even exists (
TorrentEngine.ensureClientis lazy).Two corrections to the issue text: the deferred
client.destroy()inengine.destroy()does run when the process stays alive, it just cannot complete, so it is not the cause. Andservehas had the explicit exit since 1.8.0 and could not be made to hang.The change mirrors #183: the
watchandfilesbranches get the same.then(() => process.exit(0))the other two modes have, plus one comment stating the rule every headless mode now follows. The daemon modules keep resolving from their signal handler after the synchronous flush, which is the contract that keeps them unit-testable; the CLI exits right there and lets the OS reclaim the sockets, the bargainengine.destroy()and the TUI'sforceExitalready make.Verified on the rebuilt bundle:
watchexits in 255 ms with status 0 and all three state files carry the signal's timestamp, so the flush still runs ahead of the exit;filesexits in 255 ms with a throttled download in flight.Checklist
npm run typecheckis cleannpm testpassesHELP_GROUPSandfooterHintsinsrc/ui/keymap.ts— n/aStorefield, I updatedmakeStoreinscripts/render-previews-impl.tsx— n/aprocess.exitafter the promise settles is platform-neutral; CI matrix covers all threefeat:/fix:/docs:/chore:)Generated by Claude Code