feat(shutdown): [#2169] migrate torrent cleanup to direct token-aware supervision - #2181
Conversation
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Migrates the torrent cleanup job to a directly supervised, token-aware component runner aligned with the shutdown roadmap (SI-4), replacing the legacy pre-spawned JoinHandle approach.
Changes:
- Replaces
start_job() -> JoinHandle<()>with an unspawnedrun_job(..., CancellationToken) -> Future<Output = Completion>. - Wires torrent cleanup into
JobManager::spawnusingcomponent_runner, removing the legacy registry path. - Adds lifecycle tests for token cancellation, weak-manager expiry, and supervisor outcome reporting.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/bootstrap/jobs/torrent_cleanup.rs |
Reworks cleanup job into an unspawned, token-cancellable runner and adds lifecycle tests. |
src/app.rs |
Registers torrent cleanup as a direct supervised component and adds a wiring test. |
.github/skills/dev/testing/manual-torrent-cleanup-e2e/SKILL.md |
Adds a reusable manual E2E verification procedure for torrent cleanup behavior. |
docs/issues/open/2169-1488-si-4-migrate-torrent-cleanup/verification.md |
Records updated verification evidence for deterministic cancellation and manual runs. |
docs/issues/open/2169-1488-si-4-migrate-torrent-cleanup/agent-review-reports.md |
Adds an independent reviewer report entry for SI-4. |
docs/issues/open/2169-1488-si-4-migrate-torrent-cleanup/ISSUE.md |
Updates issue metadata, tasks, checkpoints, and acceptance criteria with evidence. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
da2ce7
left a comment
There was a problem hiding this comment.
Review — SI-4 torrent cleanup migration
Reviewed at 9240464ba6334e3468dc09bbc0b99e71d0f915d1 against develop @ e4db63d5, following .github/skills/dev/pr-reviews/review-pr/SKILL.md.
The migration itself is correct and I would approve it on the code alone. Behaviour is preserved path for path, the supervision wiring is exactly what the spec's ownership invariants prescribe, the new tests are genuinely deterministic and assert the named outcome, and all gates are green. One blocking item is documentation-only.
Verification performed
Fresh detached worktree at the head sha, all gates green:
| Gate | Exit | Wall |
|---|---|---|
linter all |
0 | 36.3 s (18.0 s at the final head) |
cargo clippy --workspace --all-targets --all-features -- -D warnings |
0 | 12.1 s |
cargo test -p torrust-tracker --lib |
0 | 24.6 s — 91 passed, 0 failed |
cargo test --test lifecycle-signals |
0 | 22.3 s — 6 passed, 0 failed |
cargo test --doc -p torrust-tracker |
0 | 0.9 s — 0 doctests |
cargo test --workspace --all-targets --all-features |
0 | 145.1 s — 0 failed |
The three new tests pass by name, in both the --lib and the full-workspace runs:
app::tests::it_should_register_torrent_cleanup_as_a_direct_cancelled_component ... ok
bootstrap::jobs::torrent_cleanup::tests::it_should_return_cancelled_when_the_token_is_cancelled ... ok
bootstrap::jobs::torrent_cleanup::tests::it_should_return_completed_when_the_torrents_manager_is_dropped ... ok
All four commits are GPG-signed, Conventional Commits subjects, no AI co-author trailers, imports grouped std → external → internal, no added line over max_width = 130.
What I checked rather than assumed
- Config gate (
src/app.rs:517) is unchanged context: atinactive_peer_cleanup_interval == 0the job is not registered at all — no supervisor entry, noCompletedoutcome. - Immediate first tick (
torrent_cleanup.rs:41) is byte-identical to base, outside the loop, andMissedTickBehavioris unset at both revisions (defaultBurst). It is not a cancellation blind spot: the firsttick()isReadyon first poll. - Weak-manager lifetime.
run_jobtakes theArcby value, downgrades it, and theasync moveblock never names the outer binding (theSome(torrents_manager)at line 50 is a fresh shadow), so the strong reference is dropped whenrun_jobreturns. This is proved by execution, not only by reading the capture rules:it_should_return_completed_when_the_torrents_manager_is_droppedwould time out if the future held a strongArc, and it passes. - Cancellation cannot be lost.
cancelled()is level-triggered andInterval::tickis cancel-safe, so a cancellation not selected in one iteration is still ready in the next and no tick is consumed. Termination is guaranteed. - Drop order.
src/main.rs:8binds the container to_app_container, which lives to the end of the match arm — pastcancel()(line 13) andwait_for_all(line 15). The weak upgrade therefore cannot start failing mid-shutdown, so the shutdown outcome isCancelledand neverCompleted. That matches the recorded SIGTERM evidence and satisfies AC6. - Legacy census.
git grep ctrl_c -- src/bootstrapreturns nothing;register_legacysurvives only atapp.rs:357(udp_ban_cleanup) andapp.rs:531(peers_inactivity_update), both explicitly out of scope.torrent_cleanup::has exactly one caller. No wrapper task, noJoinHandle, no dead symbol. - Skill sample config validated field by field against
Core(#[serde(deny_unknown_fields)]) andTrackerPolicy— correct, which matters because a wrong key would abort startup. - CI at the head sha: five workflow runs registered, all queued/in-progress, no
startup_failure.
Blocker
Documentation asserting the pre-migration topology is left unchanged, while ISSUE.md:206 ticks "Documentation is updated when behavior changes".
Three artefacts the repository maintains as current state — not as dated snapshots — become false on merge:
docs/features/shutdown-process/task-inventory.md(frontmatterstatus: verified,last-updated-utc: 2026-09-08): the ownership tree (line 63), the mermaid graph (lines 84, 86), the inventory table row| Torrent cleanup | 0–1 | Legacy registry | Direct Ctrl-C | SI-4 |(line 111), the Legacy Registry prose (lines 162-165, every clause of which is now false), the direct-component count formula and its "three legacy jobs" (lines 176-181), and finding 3 (lines 191-193, "torrent_cleanupandpeers_inactivity_updatestill subscribe to Ctrl-C directly").docs/application-jobs.md:31-32: "Legacy job: one of the three pre-spawned periodic-job handles retained byJobManageroutside theJoinSetuntil SI-4/SI-5 migrate their APIs." Two after this PR, and SI-4 is done.src/bootstrap/jobs/manager.rs:182-184: an in-source instruction to future implementers,// SI-4/SI-5 must migrate these pre-spawned periodic jobs to JobManager::spawn.
I deliberately separated these from the historical record — docs/analysis/20260716-shutdown-process/, docs/issues/closed/**, and the Background sections of #1488/#1588 are dated evidence and should stay exactly as they are.
Why this blocks rather than trailing as a follow-up: task-inventory.md carries an explicit status: verified assertion of currency that this PR silently falsifies; the merge immediately preceding this PR's base is #2168 docs(shutdown): revalidate task inventory, so the project has just demonstrated it treats that document as merge-grade work, and SI-5 will be planned from it; and the acceptance criterion asserting the docs are current is ticked while they are not.
The fix is mechanical and touches no logic: move the row into Direct JoinSet Components, correct the two "three"s and the count formula, rewrite finding 3 to name SI-5 only, update the glossary term, and refresh the manager.rs comment (which is also worth making precise about udp_ban_cleanup, covered by neither SI-4 nor SI-5 — that part is pre-existing).
Suggestions
biased;in the runner'sselect!— see the inline comment. Every one of the nine existing token-pathselect!sites insrc/andpackages/uses it with the cancellation arm first.#[instrument(skip_all)]on a now-synchronous constructor — see the inline comment. It no longer spans the job's execution, and it is the onlyrun_*constructor insrc/bootstrap/jobs/that carries the attribute.
Nits
Four minor points inline (skill semantic-link convention, description trigger phrases, a base-vs-tested commit sha in verification.md, and the M3 row's substituted method).
Not raised
run_job taking config: Core and Arc<TorrentsManager> by value diverges from the sibling constructors, which take references — but the merged spec's "Design and Ownership Review" prescribes that exact signature verbatim, so it is settled and spec-conformant.
Happy to re-review promptly once the docs pass lands; nothing else stands between this and an approval.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2181 +/- ##
===========================================
+ Coverage 84.86% 84.92% +0.05%
===========================================
Files 351 351
Lines 30131 30211 +80
Branches 30131 30211 +80
===========================================
+ Hits 25572 25656 +84
+ Misses 4186 4184 -2
+ Partials 373 371 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
9240464 to
8c46ed5
Compare
df6f2c6 to
398cdfe
Compare
|
@da2ce7 The branch has been rebased onto the current |
|
@da2ce7 Follow-up: CI is now fully green on the rebased head |
da2ce7
left a comment
There was a problem hiding this comment.
Round-2 review of SI-4 torrent cleanup migration at 398cdfef8e51ed4871539cedfa9af0195e77f5f1.
Reviewed against develop @ 7abc30b2, following .github/skills/dev/pr-reviews/review-pr/SKILL.md, from scratch over the full scope rather than only the delta.
The code is finished. Everything I raised about the implementation in round 1 is genuinely fixed in the tree, and the two new commits are correct. What still blocks is the same documentation item as round 1 — and the fault for that is partly mine: my round-1 Blocker gave a list of line numbers instead of a swept inventory, so the fix pass corrected exactly the lines I named and left three more sites of the same defect standing. I have now swept exhaustively; the list below is complete, and I will approve on the next push.
Rebase separation
git range-diff of the old series against the new one shows commits 1–4 identical in content and re-hashed only:
1: 9fac6e3d = 1: cf4d4c30 feat(shutdown): migrate torrent cleanup supervision
2: 7fd68615 = 2: eafb9f94 docs(skills): add manual torrent cleanup e2e skill
3: 7ae61d3e = 3: 07ad44d0 docs(issues): record SI-4 review and apply review nits
4: 9240464b = 4: b940585c docs(issues): link PR #2181 to SI-4 spec
-: -------- > 5: 03a4238c fix(shutdown): address SI-4 review feedback
-: -------- > 6: 398cdfef test(shutdown): make cleanup expiry assertion deterministic
So all real change is in 03a4238c and 398cdfef, and the branch now sits on the current develop tip (git merge-base develop <head> = 7abc30b2 = develop).
Round-1 items — verified in the tree, not from the replies
| Item | Status | Evidence |
|---|---|---|
biased; in the runner select! |
Fixed | torrent_cleanup.rs:51, cancellation arm first. Analysis below. |
#[instrument] on the sync constructor |
Fixed | Attribute and use tracing::instrument; both gone. #[must_use] went with it, correctly: Future is already #[must_use] through the trait, so the attribute was redundant on an impl Future return. |
Skill semantic link (skill-link: + ## Skill Links) |
Fixed | torrent_cleanup.rs:31 and SKILL.md:170-173. |
| Skill description trigger phrases | Fixed | SKILL.md:3 now ends with the three phrases. |
verification.md evidence commit shas |
Fixed | now names 9fac6e3d / 7fd68615 rather than the base. |
ISSUE.md M3 Command/Steps self-consistency |
Fixed | the row now states the substitution it actually used. |
| Docs Blocker | Partly fixed — see below |
Verified rather than assumed
-
biased;order and cancel-safety. Cancellation first is the right order: with a random poll order, a token cancelled in the same poll as a due tick can lose the tie, and the tick arm then runs a fullcleanup_torrents()pass charged against the single 10-second deadline inmain.rs:15— the path that turns AC6'sCancelledintoAborted. Both branch futures are cancel-safe:CancellationToken::cancelled()is level-triggered, so a future dropped when the other arm wins resolves immediately when recreated;Interval::tickis documented cancel-safe and consumes no tick when another branch completes first.biased;cannot starve the tick arm either, since the cancellation future isPendingand cheap until cancel. The tick arm's body (cleanup_torrents().await) still runs to completion once selected, so a cancellation arriving mid-pass is observed only at the next loop entry — unchanged from the base and inherent to the shape. -
The new
#[allow(...)]is load-bearing, and its reason is true. Tested on a detached worktree at this head by patching only the server copy:- remove the whole attribute → clippy denies two errors:
needless_pass_by_valueatconfig: Coreandmanual_async_fnat the signature; - keep only
manual_async_fn→needless_pass_by_valuestill denies; - keep only
needless_pass_by_value→manual_async_fnstill denies; - swap
allowforexpect→ exits 0, so neither lint is over-broad.
Clippy's own suggestion for the first is
config: &Core, which is exactly what the stated reason rules out: a borrowed parameter cannot produce the'staticfutureJobManager::spawnrequires. Nothing to change. - remove the whole attribute → clippy denies two errors:
-
Drop soundness off-runtime. If the returned future were dropped without ever being polled, its captured
Core,Arc<TorrentsManager>andCancellationTokenwould drop off-runtime. That is sound:TorrentsManagerhas noDropimpl and holds only aCoreand anArc<InMemoryTorrentRepository>(itself anArc<Registry>) — no runtime-affine resource. Movingtokio::time::intervalinside theasync moveblock in03a4238cis a real improvement here, since building anIntervalin the synchronous constructor would panic without a reactor. -
drop(torrents_manager)and theCoredestructure behave as intended: the strongArclives in the future only until first poll, andstart_torrent_cleanuphands the future straight toJobManager::spawn, so that is the next scheduler pass. -
Test determinism. All three new tests ran 50 times each from the compiled test binary: 0 failures (3.4 s / 3.5 s / 3.5 s for the fifty runs). See the inline note on the one-poll margin.
-
Commits. All six GPG-signed and verified by GitHub, Conventional Commits subjects, no AI co-author trailers, no added
.rsline overmax_width = 130. -
CI at this head. Five workflow runs, all
completed/success, nostartup_failure.Docker E2Eshowsskipping, which is the pre-existing conditional (#2179) and unrelated to this PR.
Gates
Detached worktree at 398cdfef, per-lane target directory, nightly rustc 1.100.0-nightly (a69a63265 2026-09-03).
| Gate | Exit | Wall |
|---|---|---|
linter all |
0 | 52.9 s |
cargo clippy --workspace --all-targets --all-features -- -D warnings |
0 | 12.1 s |
cargo test -p torrust-tracker --lib |
0 | 31.9 s — 91 passed |
cargo test -p torrust-tracker --test lifecycle-signals |
0 | 21.6 s — 6 passed |
cargo test --doc -p torrust-tracker |
0 | 0.8 s — 0 doctests |
cargo test --workspace --all-targets --all-features |
0 | 158.0 s — 0 failed |
The three new tests pass by name in both the --lib and full-workspace runs.
Blocker — three more living current-state assertions of the pre-migration topology
Round 1 blocked because documentation the repository maintains as current state would become false on merge. Three of the sites I named are fixed. Three of the same class are not, because I listed line numbers rather than sweeping. Sweeping now (git grep over every non-docs/analysis/, non-docs/issues/closed/ artefact for torrent cleanup together with legacy / Ctrl-C / "three" / SI-4-pending wording), exactly these remain false after merge:
docs/features/shutdown-process/task-inventory.md:187— Findings §1 still reads "The three pre-spawned periodic jobs are a deliberately narrow compatibility registry". Two after this PR. This is in the same document whoselast-updated-utcthis PR bumps to2026-09-09understatus: verified, and whose count formula and Legacy Registry section the same commit already corrected — so the document now contradicts itself.docs/application-jobs.md:122-127("Current Limitations and Future Work") — "keeps a narrow compatibility registry for the pre-spawned torrent-cleanup, activity-metrics, and UDP ban-cleanup periodic jobs … They are transitional until SI-4/SI-5 migrate their periodic-job APIs." The glossary at:31-33in the same file was corrected; this paragraph was not.src/AGENTS.md:83-89— "The torrent-cleanup, activity-metrics, and UDP ban-cleanup jobs retain their pre-existing starter and cancellation semantics: the first two listen for Ctrl-C …register_legacy(name, handle)retains their handles in a narrow compatibility registry … This transitional exception is expected to be removed by the SI-4/SI-5 periodic-job migrations." Every clause of this is false for torrent cleanup after merge. This is the one I most regret missing: it is the architecture contract the SI-5 implementer will read, and the review checklist carries an explicit "AGENTS.mdupdated if architecture changed" item that is otherwise unmet. (src/AGENTS.md:46only names the starter in the call tree and stays correct.)
Everything else the sweep surfaced is legitimately untouched and I am not asking for it: the #1488 roadmap row (Open #2169, flipped on close), the #1586/#1588 records and their Background sections, this issue's own folder, and docs/analysis/** / docs/issues/closed/**. That is the complete list; there is no further docs round after this one.
Minor
One inline: the ownership tree line added for torrent cleanup is indented four spaces where every sibling uses six, so the connector does not line up in the rendered block.
Suggestions and nits
Two inline: the one-scheduler-poll margin in the paused-time expiry assertion (with the experiment that measures it), and an optional expect-over-allow note. Plus a nit on the ragged re-wrap at task-inventory.md:180-181 ("The / two legacy jobs are separate and individually / conditional as shown above.") — worth reflowing while the paragraph is being touched for the Blocker anyway.
Not raised
run_job taking config: Core by value clones the whole Core to read one u64, and diverges from the sibling constructors that take references — but the merged spec's "Design and Ownership Review" prescribes that exact signature, the 'static requirement justifies it, and PR #2171 already settled it. docs/issues/open/1588-…/verification.md:38 still shows SI-4 as Pending, but that table's column is "Status of this review" — a dated snapshot of that review, which should stay as it is.
Happy to re-review immediately; nothing but these three doc paragraphs stands between this and an approval.
|
@da2ce7 Round-2 feedback is addressed in |
cb7a146 to
a5fecc8
Compare
|
@da2ce7 Correction to the prior handoff: the review-fix commit was amended solely to include formatter-required list indentation in , so its final hash is |
|
@da2ce7 Correction to the prior handoff: the review-fix commit was amended solely to include formatter-required list indentation in |
a5fecc8 to
46f5727
Compare
|
@da2ce7 Final rebase update: the branch is now rebased onto current |
da2ce7
left a comment
There was a problem hiding this comment.
Reviewed at 46f572749d7c714d8b028e13cc003e05a03ca7a9.
APPROVE. All five round-2 items are addressed in the tree, the three paragraphs that blocked round 2 are now factually true after merge, and the code change that came with them is an improvement I asked for and re-measured.
The three round-2 blocking paragraphs, now discharged
Verified against the tree, not against the reply text.
docs/features/shutdown-process/task-inventory.mdFindings §1 — now separates the historical registry from the current one ("The three pre-spawned periodic jobs were a deliberately narrow compatibility registry. The two remaining pre-spawned periodic jobs are retained in that registry under the same process-wide deadline."). Two is the right number:src/app.rsat this commit has exactly tworegister_legacycall sites,udp_ban_cleanup(:357) andpeers_inactivity_update(:531), and torrent cleanup goes throughjob_manager.spawn("torrent_cleanup", …)at:518. The file no longer contradicts its own:86,:112,:181and its Legacy Registry section, which lists exactly those two bullets.docs/application-jobs.md:123-128— the Current Limitations paragraph now names only activity metrics and UDP ban cleanup and states the remaining path as SI-5 plus the UDP ban-cleanup periodic-job migration. It matches the glossary at:31-33word for word in substance.src/AGENTS.md:83-90— rewritten. "Torrent cleanup is a directJoinSetcomponent that receives the manager's cancellation token" is true (app.rs:518-525, token fromjob_manager.new_cancellation_token()); "the former listens for Ctrl-C and the latter keeps its existing manager token" is true for the two survivors (activity_metrics_updater::start_job(config, app_container)takes no token;start_ban_cleanup_job(…, job_manager.new_cancellation_token())does);register_legacyretaining "those two handles" matches the two call sites; and the removal clause now points at SI-5 and the UDP migration rather than SI-4.
I re-swept the whole tree for this class of statement at this commit — three pre-spawned, three (legacy|pre-spawned|periodic), torrent-cleanup, activity-metrics, SI-4/SI-5, Ctrl-C, and every torrent cleanup line in *.md and *AGENTS.md, excluding docs/analysis/ and docs/issues/closed/. Nothing of the class survives. Everything still matching is a record rather than a current-state claim: this issue's own spec Background, the #1488 roadmap entry, the #1586 and #1588 review records and their verification tables, the docs/copilot-pr-reviews/ logs, and packages/tracker-core/migrations/README.md (three database migrations, unrelated).
The other three items
- Round-2 Minor, tree-diagram indent — fixed.
task-inventory.md:49now carries six leading spaces like every sibling; checked byte-for-byte withcat -A, the│connectors line up. - Round-2 Suggestion, expiry-test margin — applied as a bounded yield loop, and I measured what it bought. Patching the bound to 0, 1 and 2 in turn:
0fails deterministically at the assertion (exit 101),1passes,2passes. So the assertion still needs exactly one poll and the loop grants eight, with an earlybreakonis_finished()so the extra bound costs nothing when the task is already done. The coupling totokio::time::advance's internal yield accounting is gone: a tokio release adding a wake step insideadvancenow has seven polls of headroom instead of zero. 50/50 runs green for each of the three tests (3.40 s / 3.40 s / 3.33 s). - Round-2 Nit,
expectoverallow— taken.#[expect(clippy::manual_async_fn, clippy::needless_pass_by_value, reason = …)]compiles clean under-D warnings, so both expectations are still fulfilled and the suppression will now self-invalidate if either lint stops firing.
Verification
Re-run in full after the rebase onto develop at f6b73e29. The rebase is content-free: git range-diff 7abc30b2..a5fecc82 f6b73e29..46f57274 marks all seven commits =, and comparing the two base-to-head patches with blob hashes stripped leaves only three src/app.rs hunk headers whose line numbers shift by the eighteen lines #2178 added above them — not one content line differs. Gates were re-run anyway, because the tree underneath the commits is new.
Detached worktree at 46f57274, clean tree, nightly cargo 1.100.0-nightly (b2e9d5f9d 2026-09-02) / rustc 1.100.0-nightly (a69a63265 2026-09-03):
| Step | Exit | Wall |
|---|---|---|
linter all (markdown, lychee, yaml, toml, cspell, clippy, rustfmt, shellcheck) |
0 | 27.7 s |
cargo clippy --workspace --all-targets --all-features -- -D warnings |
0 | 7.7 s |
cargo test -p torrust-tracker --lib |
0 | 14.5 s — 96 passed |
cargo test -p torrust-tracker --test lifecycle-signals |
0 | 20.4 s — 13 passed |
| expiry / cancel / wiring tests × 20 each | 0 | 1.34 s / 1.38 s / 1.35 s — 0 failures |
The lib and lifecycle-signals counts rose from 91 and 6 to 96 and 13, which is the point of re-gating: those are #2178's tests, and they pass alongside this PR's changes on the merged tree. The earlier measurements that are not repeated here — the bound-margin probe showing the expiry assertion needs one poll where the loop grants eight, and the clippy experiments proving both #[expect] entries load-bearing — were made against identical source and stand.
The branch now sits directly on develop at f6b73e29, so the merge is a fast-forward: git merge-tree --write-tree develop refs/quarantine/pr-2181 produced tree 7f19586f, which is exactly the branch head's own tree.
The one optional note I had left — that the two replacement lines in Findings §1 were indented two spaces where their neighbours use three — was taken in the amendment from cb7a1467, whose only change is exactly those two characters of indentation (git diff -w between the two commits is empty).
|
ACK 46f5727 — rebase onto develop f6b73e2 verified content-free (range-diff: all seven commits identical), all three round-2 documentation blockers verified true against src/app.rs, full-tree sweep clean, and the full gate re-run green on the new base: linter all 27.7 s, clippy -D warnings 7.7 s, 96 lib tests, 13 lifecycle-signals tests, 20x3 stress runs with no failures; merges into develop as a fast-forward. |
Record the independent Task Reviewer report for torrust#2169, complete the issue checkpoints, and apply its non-blocking nits: skip all fields in the cleanup runner instrument span and make the drop-before-tick Arrange condition explicit in the weak-manager expiry test.
Prioritize cleanup cancellation, link the manual E2E skill to its runner, and refresh the current shutdown ownership inventory after the SI-4 migration.
Replace a paused-Tokio-time timeout with an explicit post-advance completion assertion.
Update the remaining current-state shutdown ownership records after migrating torrent cleanup, and make the expiry test less sensitive to Tokio scheduler internals.
46f5727 to
123e49c
Compare
|
ACK 123e49c |
|
Hi @da2ce7 I've rebased, can you ACK the new commit? |
|
Re-verified at |
da2ce7
left a comment
There was a problem hiding this comment.
Reviewed at 123e49cb9915c1e7a2d8c54e553c968a55079065.
APPROVE, re-affirming my round-3 approval on this head. The rebase onto develop at 0e61ec8a is content-free and I re-ran the verification at this commit rather than inheriting it.
The rebase carries no content
git range-diff f6b73e29..46f57274 0e61ec8a..123e49cb marks all seven commits = — identical content, re-hashed only:
1: 0383bc1e = 1: f2a82885 feat(shutdown): migrate torrent cleanup supervision
2: 6362c599 = 2: e70263b7 docs(skills): add manual torrent cleanup e2e skill
3: d09fce5d = 3: ef0d5ff5 docs(issues): record SI-4 review and apply review nits
4: bb34aa15 = 4: 3308f0b9 docs(issues): link PR #2181 to SI-4 spec
5: 9dd21e51 = 5: 5a4c8455 fix(shutdown): address SI-4 review feedback
6: 4a30ebcf = 6: 44c6e5b8 test(shutdown): make cleanup expiry assertion deterministic
7: 46f57274 = 7: 123e49cb docs(shutdown): complete SI-4 ownership updates
Confirmed a second way: the base-to-head patch at this head is byte-identical to the one at 46f57274 once blob-hash index lines are stripped — not even a hunk-header offset differs this time, because the new base commits touch no file this PR touches (git diff --name-only of the two ranges share nothing). Both pinnings report the same 10 files changed, 503 insertions(+), 100 deletions(-).
The base delta f6b73e29..0e61ec8a is the #2186 documentation merge: three files under docs/issues/ and docs/copilot-pr-reviews/, and git diff --stat f6b73e29 0e61ec8a -- src packages tests Cargo.toml Cargo.lock is empty, so no Rust source or manifest moved underneath this branch.
Verification re-run at this head
Detached worktree at 123e49cb, clean tree, nightly cargo 1.100.0-nightly (b2e9d5f9d 2026-09-02) / rustc 1.100.0-nightly (a69a63265 2026-09-03):
| Step | Exit | Wall |
|---|---|---|
linter all (markdown, lychee, yaml, toml, cspell, clippy, rustfmt, shellcheck) |
0 | 18.0 s |
cargo clippy --workspace --all-targets --all-features -- -D warnings |
0 | 0.7 s |
cargo test -p torrust-tracker --lib |
0 | 1.1 s — 96 passed |
cargo test -p torrust-tracker --test lifecycle-signals |
0 | 10.8 s — 13 passed |
The three tests this PR adds pass by name in the --lib run. The cargo steps are near-instant because the code tree is byte-identical to the one I gated at 46f57274, which is itself the evidence that nothing compiled changed.
The branch fast-forwards onto develop: git merge-base --is-ancestor develop refs/quarantine/pr-2181 is true and git merge-tree --write-tree produced tree b432d277, which is exactly the branch head's own tree — nothing to resolve.
What stands from round 3
Everything, since the source is unchanged: the three documentation paragraphs that blocked round 2 are correct after merge and were checked against src/app.rs (exactly two register_legacy sites, torrent cleanup registered through spawn with the manager's token); the full-tree sweep for pre-migration current-state claims returns nothing; the bounded-yield expiry test was measured to need one scheduler poll where the loop grants eight; and both #[expect] entries are load-bearing with an accurate reason. All 17 review threads are resolved.
One housekeeping note for whoever merges: my round-3 approval was submitted without an explicit commit pin and GitHub attached it to this head, while the ACK comment posted alongside it names 46f57274. The ACK below supersedes it and names this commit.
Summary
Implements SI-4 of the #1488 shutdown roadmap: torrent cleanup becomes a directly supervised, token-aware top-level component instead of a pre-spawned job in the
JobManagerlegacy registry.torrent_cleanup::start_job() -> JoinHandle<()>with an unspawnedrun_job(config, torrents_manager, cancellation_token) -> impl Future<Output = Completion> + Send + 'static.tokio::signal::ctrl_c()dependency; it now stops oncancellation_token.cancelled()and returnsCompletion::Cancelled, orCompletion::Completedwhen the weakTorrentsManagerreference expires.src/app.rsregisterstorrent_cleanupdirectly throughJobManager::spawn+component_runner; no cleanupJoinHandle, wrapper task, orregister_legacycall remains.JobManager, cleanup policy,peers_inactivity_update(SI-5), and UDP ban cleanup are untouched.torrent_cleanup: JobStatus::Cancelledsupervisor outcome.manual-torrent-cleanup-e2eskill in a separate commit, as required by the spec.Closes #2169
Files touched
src/bootstrap/jobs/torrent_cleanup.rssrc/app.rs.github/skills/dev/testing/manual-torrent-cleanup-e2e/SKILL.mddocs/issues/open/2169-1488-si-4-migrate-torrent-cleanup/(ISSUE.md,verification.md,agent-review-reports.md)Validation
cargo test -p torrust-tracker --lib(all root library tests pass, including the three new lifecycle tests)Stopping torrent cleanup job ...andJob completed after cooperative cancellation job=torrent_cleanupbeforeTorrust tracker successfully shutdown.; no deadline abort fortorrent_cleanup.inactive_peer_cleanup_interval = 1,max_peer_timeout = 1,remove_peerless_torrents = false: the announced peer is removed while the torrent remains queryable ("peers": [], HTTP 200). Details inverification.md.linter all,git diff --check, mandatory pre-commit checks, and pre-push checks (nightly fmt/check/doc, full test suite).The isolated verification used a git-ignored
.tmp/configuration only; no temporary local patches are part of this branch.