usenet-drain-walk: run the drain down one pass at a time, under a watchdog - #121
Merged
Merged
Conversation
…chdog The drain has been held down since 2026-09-20 and the documented way to work it off is hand-running passes and reading each one. At 607 NZBs that stops being practical, and the alternative -- arming the timer -- is what wedged the box in the first place. This is the manual procedure with the reading done by a rule: one bounded pass in the foreground, a progress fingerprint (outbox depth, watch folder contents, staging bytes, state file size and mtime) sampled every 30s, and the pass's whole process group killed when none of it has moved for ten minutes. Four passes in a row that moved nothing ends the walk. Killing a pass moves the walk on rather than repeating it, and nothing here has to remember which release was stuck: run() marks `last_fetch_attempt` before it fetches and the fetch set is least-recently-attempted first, so a killed pass sends its releases to the back. That holds across a SIGKILL. It never writes the state file (the Python half's, on a resume contract), never deletes an NZB, and never arms, stops or disables usenet-blackhole.timer -- it refuses to run while that timer is active instead, because two drains on one outbox is the load this exists to avoid. It exits 0 when the outbox ends below the mark and 3 when it does not. 16 bats tests, and a mutation corpus entry: the tests are killed by a slack outbox comparison and by a kill that reaches the shell but not the fetch, and the second one is what the group signal is for.
CI caught two guards that a new script is supposed to trip: the scripts tree in CONTRIBUTING.md and the no-sweep list in tests/mutation/README.md. Both compare against the tree on disk, so both were correct to fail -- that is the intended cost of adding a script rather than a reason to widen either list. usenet-drain-walk.sh gets a line in each, and a description in the tree.
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.
Why
The drain has been held down since 2026-09-20 (documented in
docs/MAINTENANCE.md, "Holding the usenet ingest down"), and the sanctioned way to work it off is hand-running passes and reading each one. At 607 NZBs that stops being practical, and the alternative — arming the timer — is what wedged the box in the first place.This is that manual procedure with the reading done by a rule.
What it does
--report-failures --max-inflight 6).Killing a pass moves the walk on rather than repeating it, and nothing has to remember which release was stuck:
run()markslast_fetch_attemptbefore it fetches and the fetch set is least-recently-attempted first, so a killed pass sends its releases to the back. That holds across a SIGKILL, not just a SIGTERM.What it deliberately never does
logs/usenet-blackhole-state.json— the Python half's file, on a resume contract, and a second writer on it is the coupling trap the pressure gate avoids on purpose.usenet-blackhole.timer. It refuses to run while that timer is active instead, because two drains on one outbox is the load this exists to avoid.Verification
tests/usenet-drain-walk.bats), all passing locally.usenet-blackhole.shprints (a test asserts the two agree, because a driver watching a different folder reads every running pass as stalled):21 of those 22 jobs are complete at TorBox and owed a local fetch, ~99 hours old: nothing has drained since 2026-09-20 21:25.