Skip to content

feat(configuration): [#2668] configure the shutdown policy and document the deployment contract - #2678

Merged
josecelano merged 38 commits into
torrust:developfrom
josecelano:2668-configure-shutdown-policy
Oct 28, 2026
Merged

josecelano merged 38 commits into
torrust:developfrom
josecelano:2668-configure-shutdown-policy

Conversation

@josecelano

@josecelano josecelano commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Closes #2668

Summary

This implements SI-20 of EPIC #1488. The tracker now reads its shutdown budgets from a new optional [application.shutdown] configuration table, validates them at startup, injects each value into the component that applies it, and exits with code 0 only after a complete stop (and 1 after a failed, panicked or aborted component, or a failed startup). The deployment contract (the container runtime waits at least the process deadline plus 5 s) is documented and was demonstrated with Docker and systemd.

Behavior changes operators will notice

The process deadline defaults to 25 s (it was a hard-coded 10 s). The HTTP tracker and REST API drain defaults to 15 s (it was a hard-coded 90 s), and the health check API drain rises from 5 s to 15 s because it shares the HTTP drain. The UDP request drain stays 5 s and the event listener drain stays 5 s. A requested stop that is incomplete now exits 1, so a systemd unit is marked failed; do not add 1 to SuccessExitStatus. Docker and Podman's default 10 s stop timeout is shorter than the default deadline and ends in exit status 137; docs/containers.md gives the 30 s minimum and 35 s recommended settings. The tracker refuses to start unless max(http_drain_in_secs, udp_request_drain_in_secs) + event_listener_drain_in_secs is below deadline_in_secs. These changes need a line in the release notes. The server crates (0.1.0) change signatures (Launcher::new, the UDP and health check start_with_cancellation), as decided in D5 of the specification.

What changed

Configuration: packages/configuration gets the [application.shutdown] table with value newtypes (1 to 3600 s), the chain-rule validator and a named accessor per budget. Behavior: src/main.rs returns an ExitCode from a pure outcome mapping in src/bootstrap/jobs/manager.rs; the deadline, drains and listener budget are injected from src/app.rs and src/bootstrap/jobs/ into axum-http-server, axum-rest-api-server, axum-health-check-api-server and udp-server, so the constants in those crates are gone; the shared drain helper in axum-server now bounds an oversized timeout. Tests: unit tests per crate, the lifecycle-shutdown-policy native-binary target (real binary, exact PID, SIGTERM and SIGINT), and a template test; the shared native-tracker fixture's shutdown() now requires exit status 0. Documentation and decisions: the five container and development templates, docs/containers.md, the three E2E Compose files (stop_grace_period: 35s), the architecture documents and glossary, the v2 to v3 migration guide, an amendment to Q4, and two root ADRs (the [application] placement rule and the budget model).

Validation

linter all exit 0; cargo test --tests --benches --examples --workspace --all-targets --all-features 3,664 passed, 0 failed (on the tree rebased the second time, which includes the tests develop gained); cargo test --doc --workspace exit 0; the pre-push script exit 0 on that tree. Every new test was mutation-proven (the mutation fails it, the restore is proven with cmp); the tables are in manual-verification-evidence.md. Manual verification (V1 to V9 in that file) ran on the real binary and a release image: defaults, a held connection under a 2 s drain (exit 1), invalid configurations rejected before any listener binds, the V1 fault-injection recheck, Docker with a 35 s and the default 10 s stop timeout, systemd, and a UDP tracker with distinct budgets. Podman was not run (not installed). An independent review first returned REVIEW FAILED for documentation findings, all addressed, and the re-review returned REVIEW PASSED (agent-review-reports.md).

The temporary fault-injection patch used for V1, V5 and V7 is not part of this branch; the working tree was proven identical to HEAD after each use. This is not a performance-critical path (shutdown-time configuration), so no benchmark was run.

For the maintainer

No acceptance criterion is ticked. Open items: AC5 has no maintained test guarding which key reaches the UDP drain in src/app.rs (V9 shows it on the real binary) or the listener drain (source-level guard only); several native-binary tests keep their Act in a helper; JobManager::wait_for_all still adds to the clock unchecked (only the 3600 s configuration bound protects it), a follow-up candidate recorded in implementation-retrospective.md. The history of this branch was rewritten twice with a rebase onto the latest develop, both times because another pull request merged after this one was opened and conflicted with it. The first conflict (pull request 2676) was an import line in the shared native-tracker fixture's test module, resolved by keeping both sets of tests. The second (pull request 2680, announce-interval bounds) was in packages/configuration/docs/migrate-v2-to-v3.md, where both pull requests added a "Step 11", a table row and a checklist row: the announce-interval step keeps number 11, the shutdown-budget step is now Step 12, and both rows and checklist items are kept. All commits were re-signed each time. No repository file cites a commit id, only Conventional Commit subjects. The manual verification ran before the rebases on code they did not change. Head to merge: the commit docs(issues): record the second rebase onto develop for #2668. Changed since the previous head (docs(issues): record how the listener drain budget reaches the listeners for #2668, before the second rebase): only the rebase onto the current develop and one progress-log entry that records it. The previous head's own change was the following: the maintainer decided that the seven event listeners read their drain budget from app_container.application_config like the other budgets, so the event_listener_drain_timeout(config) helper is removed, four listener starters and their wrappers in src/app.rs drop an unused config parameter, and the UDP banning starter has its develop signature again; three starter tests use the container; the stamp of the shutdown feature README is bumped and every changed file's stamp was re-checked; the specification and the evidence describe the change. The review of Copilot (one finding about which drain timeouts end a stop with exit code 1) was processed in docs/pr-reviews/pr-2678-review/PR-REVIEW.md. A push dismisses reviews.

Copilot AI balanced review requested due to automatic review settings October 28, 2026 09:33
@josecelano josecelano self-assigned this Oct 28, 2026
@josecelano
josecelano requested a review from da2ce7 October 28, 2026 09:34
Copilot stopped reviewing on behalf of josecelano due to an error October 28, 2026 09:54
@josecelano
josecelano force-pushed the 2668-configure-shutdown-policy branch from af3dffd to 669411a Compare October 28, 2026 10:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The shutdown documentation needs to distinguish HTTP-family drain failures from UDP and event-listener timeouts, which can discard work while still allowing exit code 0 and a success message.

1 open issue
What changed in this PR

This PR implements SI-20 of the shutdown overhaul: configurable shutdown budgets, startup validation, and process exit codes based on component outcomes. It also documents the deployment grace periods needed to accommodate the tracker’s shutdown deadline.

Changes:

  • Adds optional [application.shutdown] settings and injects budgets into the supervisor, servers, and event listeners.
  • Adds outcome-based exit reporting, native-binary shutdown tests, and configured-drain tests.
  • Updates configuration templates, deployment guidance, architecture decisions, and Compose stop timeouts.
File Description
tests/​lifecycle/​shutdown_policy.rs Tests signals, exit codes, deadlines, and invalid configuration.
tests/​common/​native_tracker/​mod.rs Exposes child exit status and captured output.
tests/​common/​native_tracker/​health.rs Generalizes service-address log parsing.
tests/​common/​native_tracker/​failed_start.rs Adapts command construction to the new parameter.
tests/​common/​native_tracker/​command.rs Adds isolated shutdown-budget overrides.
src/​main.rs Applies the configured deadline and reports shutdown outcomes.
src/​container.rs Retains application settings for runtime consumers.
src/​bootstrap/​jobs/​udp_tracker.rs Passes the UDP request-drain budget.
src/​bootstrap/​jobs/​udp_tracker_server.rs Configures statistics and banning listener drains.
src/​bootstrap/​jobs/​udp_tracker_core.rs Configures the UDP-core listener drain.
src/​bootstrap/​jobs/​tracker_core.rs Configures tracker-core listener drains.
src/​bootstrap/​jobs/​tracker_apis.rs Injects the REST API drain budget.
src/​bootstrap/​jobs/​torrent_repository.rs Configures the repository listener drain.
src/​bootstrap/​jobs/​mod.rs Centralizes listener-budget access and wiring checks.
src/​bootstrap/​jobs/​manager.rs Maps component outcomes to exit codes.
src/​bootstrap/​jobs/​http_tracker.rs Injects the HTTP tracker drain budget.
src/​bootstrap/​jobs/​http_tracker_core.rs Configures the HTTP-core listener drain.
src/​bootstrap/​jobs/​health_check_api.rs Injects the health-check drain budget.
src/​bootstrap/​config.rs Checks shutdown defaults in shipped templates.
src/​app.rs Wires budgets into startup cleanup and services.
src/​AGENTS.md Updates shutdown behavior documentation.
share/​default/​config/​tracker.development.sqlite3.toml Adds default shutdown settings.
share/​default/​config/​tracker.container.sqlite3.toml Adds shutdown defaults and runtime guidance.
share/​default/​config/​tracker.container.postgresql.toml Adds shutdown defaults and runtime guidance.
share/​default/​config/​tracker.container.no-persistence.toml Adds shutdown defaults and runtime guidance.
share/​default/​config/​tracker.container.mysql.toml Adds shutdown defaults and runtime guidance.
packages/​udp-server/​src/​testing/​environment.rs Supplies the default UDP drain budget.
packages/​udp-server/​src/​server/​states.rs Accepts a request-drain duration at startup.
packages/​udp-server/​src/​server/​mod.rs Updates server tests for budget injection.
packages/​udp-server/​src/​server/​launcher.rs Uses injected budgets and tests the public startup path.
packages/​events/​src/​drain.rs Clarifies configured versus standalone drain defaults.
packages/​configuration/​src/​validator.rs Adds an actionable budget-consistency error.
packages/​configuration/​src/​v3_0_0/​shutdown.rs Defines validated budgets, defaults, accessors, and tests.
packages/​configuration/​src/​v3_0_0/​mod.rs Integrates application settings into schema v3.
packages/​configuration/​src/​v3_0_0/​application.rs Adds the optional application configuration section.
packages/​configuration/​docs/​migrate-v2-to-v3.md Explains shutdown-related migration considerations.
packages/​axum-server/​src/​signals.rs Bounds oversized drain durations.
packages/​axum-rest-api-server/​src/​testing/​environment.rs Supplies configured REST API drain durations.
packages/​axum-rest-api-server/​src/​server.rs Applies and tests injected REST API drain budgets.
packages/​axum-http-server/​src/​testing/​environment.rs Supplies default HTTP drain durations.
packages/​axum-http-server/​src/​server.rs Applies and tests injected HTTP drain budgets.
packages/​axum-health-check-api-server/​src/​server.rs Accepts and tests configurable connection drains.
packages/​axum-health-check-api-server/​src/​environment.rs Supplies the shared HTTP drain default.
docs/​issues/​open/​2668-1488-si-20-configure-shutdown-policy/​ISSUE.md Records implementation progress, decisions, and verification.
docs/​issues/​open/​2668-1488-si-20-configure-shutdown-policy/​implementation-retrospective.md Records lessons and remaining follow-up candidates.
docs/​issues/​open/​2668-1488-si-20-configure-shutdown-policy/​evidence/​hold-and-stop.sh Removes the disposable verification script.
docs/​issues/​open/​2668-1488-si-20-configure-shutdown-policy/​evidence/​baseline.toml Removes the disposable baseline configuration.
docs/​issues/​open/​2668-1488-si-20-configure-shutdown-policy/​agent-review-reports.md Records independent review results and follow-ups.
docs/​features/​shutdown-process/​README.md Documents implemented shutdown budgets.
docs/​features/​shutdown-process/​questions.md Amends the budget decision and links evidence.
docs/​containers.md Documents exit codes and deployment stop timeouts.
docs/​architecture/​tracker-instance-architecture.md Explains application-level shutdown configuration.
docs/​architecture/​glossary.md Updates drain and deadline definitions.
docs/​architecture/​application-jobs.md Documents the configurable process deadline.
docs/​adrs/​index.md Registers both shutdown-related decisions.
docs/​adrs/​20261027145446_budget_shutdown_in_two_chained_stages_inside_one_process_deadline.md Records the budget model and deployment contract.
docs/​adrs/​20261027145429_place_process_wide_settings_in_the_application_configuration_section.md Establishes process-wide configuration placement.
compose.qbittorrent-e2e.sqlite3.yaml Sets a 35-second tracker stop grace period.
compose.qbittorrent-e2e.postgresql.yaml Sets a 35-second tracker stop grace period.
compose.qbittorrent-e2e.mysql.yaml Sets a 35-second tracker stop grace period.
Cargo.toml Registers the shutdown-policy integration test target.

Review effort: Balanced

josecelano and others added 18 commits October 28, 2026 13:19
…rule validation

Add an optional [application.shutdown] table to the v3 configuration with four
budgets in whole seconds: deadline_in_secs (25), http_drain_in_secs (15),
udp_request_drain_in_secs (5) and event_listener_drain_in_secs (5). Each value
is a ShutdownBudgetInSecs, so zero and values above 3600 are rejected when the
configuration loads, and unknown keys are rejected.

Shutdown has two sequential stages, because event listeners are stopped only
after the servers that publish to them finish. Configuration::validate therefore
rejects a configuration where max(http drain, udp request drain) plus the event
listener drain is not below the deadline, with an error that names the keys,
their values and what to change. With the previous 20 s HTTP drain the chain
would be 25 s, which leaves the listeners no time.

Nothing consumes the budgets yet: wiring them into the supervisor and the
servers follows after the design review of the specification. Record the 25 new
tests, their prose-first comparison and 25 mutation proofs in the issue-local
manual-verification-evidence.md.

Related to torrust#2668

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rust#2668 shutdown budgets

The maintainer decided D5: inject the shutdown values from the composition root
to the components that apply them, change signatures where needed, pass a plain
Duration where a callee needs only a value, and add a public default constant
only where injection would add an unwanted dependency. Update the delivery
section, T5 and the decision table, and supersede the earlier proposal of
additive builders with separate crate defaults.

Related to torrust#2668

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed shutdown

Add shutdown_exit_code, which maps the outcomes of a shutdown to the process
exit code, returning the current behavior of main: exit 0 whatever the
outcomes. Add five unit tests: no outcomes and only completed or cancelled
components must give 0, and a failed, a panicked and an aborted component must
give 1. The last three fail, which is the defect of exit code 0 after a failed
shutdown; the next commit makes them pass. Record the red run in the issue-local
manual-verification-evidence.md.

Related to torrust#2668

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… aborted at shutdown

main discarded the outcomes of the shutdown and always logged success, so the
process exited with code 0 even when a component failed, panicked or was aborted
at the deadline. Map the outcomes to the exit code: 0 only when every component
completed or was cancelled, 1 otherwise. main now returns that exit code, logs
the success message only on exit 0, and otherwise logs an error that names the
incomplete components. A startup failure returns the failure code instead of
calling process::exit.

The three tests added in the previous commit pass. Record the green run, six
mutation proofs and the one process mistake in the issue-local
manual-verification-evidence.md. The real-binary recheck follows after the last
production change.

Related to torrust#2668

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tener drain budget

Read the process shutdown deadline from the configuration instead of a hard-coded
10 s: AppContainer now carries the application settings, main reads the deadline
from it, and the cleanup after a failed startup reads it from the configuration.
Pass application.shutdown.event_listener_drain_in_secs to all seven event
listeners through one helper, event_listener_drain_timeout, in place of the
default constant of the events package. The banning listener gains a
configuration parameter like the other listener sites.

Add ShutdownBudgetInSecs::as_duration so a callee receives a plain Duration, and
four tests. The drain timeout of a listener has no observable seam in a unit test,
so a source-level test checks that every listener site passes the configured
value; a surviving mutation made it count the call per listener. Record the
tests, their prose-first comparison and the mutation proofs in the issue-local
manual-verification-evidence.md.

Related to torrust#2668

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t overflow the clock

The drain helper added the caller's Duration to the clock, which panics when the sum does not fit. Its parameter is a plain Duration (maintainer decision D5 in torrust#2668), so any value can reach it. A timeout longer than one year is now shortened to one year. The new test passes Duration::MAX and fails with the tokio overflow panic when the bound is removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…P tracker

The Launcher takes a drain_timeout and the token-aware runtime passes it to the drain helper, so the hard-coded 90 s constant is gone. The composition root passes application.shutdown.http_drain_in_secs; the test environment and fixtures pass the configuration default. The new test holds a connection open with a partial request header under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… REST API

The Launcher takes a drain_timeout and the token-aware runtime passes it to the drain helper, so the hard-coded 90 s constant is gone. The REST API start function builds the Launcher and hands it to start_v1, which keeps that function within the argument limit. The composition root passes application.shutdown.http_drain_in_secs. The new test holds a connection open under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t to the health check API

start_with_cancellation takes a drain_timeout and passes it to the drain helper, so the hard-coded 5 s constant is gone. The health check API follows the HTTP drain budget, application.shutdown.http_drain_in_secs, whose default is 15 s; the previous 5 s is therefore a default change for operators. The new test holds a connection open under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…UDP tracker

start_with_cancellation takes a request_drain_deadline and start_receive_loop passes it on, so the hard-coded 5 s REQUEST_DRAIN_DEADLINE and its default wrapper are gone. The composition root passes application.shutdown.udp_request_drain_in_secs. The legacy launcher path, which SI-19 removes, uses the configuration default. The receive-loop shutdown scenarios now start through the public token-aware start, so their 100 ms deadline reaches the receive loop through it; ignoring it makes the orphaned-processors scenario fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s for torrust#2668

Mark T5 done, add the T5 evidence section (design, prose-first comparison, mutation proofs, automatic checks) and a progress-log entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orrust#2668

The new lifecycle-shutdown-policy target starts the real binary with configured shutdown budgets and signals its exact PID. It checks exit code 0 and the default 25 s deadline on a clean stop with SIGTERM and SIGINT, the configured deadline in the supervisor log, exit code 1 when a connection held open outlasts the configured HTTP drain (SIGTERM and SIGINT) with no success log, exit code 1 and the configured deadline when a service fails to start, rejection of a zero deadline and of budgets that break the chain rule before any service starts, and acceptance of the largest valid HTTP drain.

The shared fixture gains shutdown-budget overrides, an exit-status result, the HTTP tracker address, and an ANSI-free output view. Its shutdown method now requires exit status 0; it ignored the status before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The three drain-timeout tests of the HTTP, REST API and health check servers and the native-binary helper confirmed that the server had accepted the held connection with an unbounded reqwest call, which would hang the test when the server never answers. Each probe now has a five-second bound. The earlier commits of these tests are kept as they are.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark T6 done, add the T6 evidence section (tests, deviations, prose-first comparison, mutation proofs, automatic checks) and a progress-log entry. Record that the disposable script stays until T8 has used it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d templates

The four container templates and the development template now carry the [application.shutdown] block with the default values and a comment that gives the orchestrator rule and the chain rule. A test requires each of them to state the block and to equal the defaults, because a template without the block would also load. It fails when a value differs and when the block is removed. The e2e and benchmarking templates are not operator-facing and are left as they are.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e period

The three qBittorrent E2E Compose stacks stopped the tracker with Docker's 10 s default, which is shorter than the default 25 s process deadline. They now set stop_grace_period to 35s, the recommended value of the deployment contract in docs/containers.md. The Compose files were validated with docker compose config; the E2E stacks themselves were not run here and run in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… for torrust#2668

A new section, Stopping the Tracker, lists the four [application.shutdown] keys and their defaults, the chain rule, the environment override form, the exit codes, and the deployment contract: the container runtime waits at least the deadline plus 5 s (30 s minimum, 35 s recommended) with the Docker, Compose, Kubernetes, and systemd settings. It says plainly that Docker and Podman's 10 s default ends in exit status 137 and that exit code 1 must not be added to SuccessExitStatus.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… of torrust#2668

Two root ADRs: the rule that places process-wide settings in an optional [application.<topic>] sub-table, and the budget model of four configured shutdown budgets with the startup rule max(HTTP drain, UDP drain) + listener drain < deadline, the orchestrator contract, and the exit-code contract. Both are registered in the ADR index.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
josecelano and others added 20 commits October 28, 2026 13:20
…dgets make stale

The application-jobs and instance architecture documents, the glossary entries for the drain timeouts and the shared deadline, the feature README, and the DEFAULT_DRAIN_TIMEOUT documentation no longer describe the 10 s deadline, the 90 s HTTP drain, or a future shutdown configuration. They state the configured budgets and their defaults and link the new ADRs. The v2 to v3 migration guide gains an optional step for [application.shutdown], with the changed defaults and the exit-code change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… drain default

Q4 gains an amendment: the event-listener drain is a fourth budget, the startup rule is max(HTTP drain, UDP drain) + listener drain < deadline, and the default HTTP-family drain is 15 s because 20 + 5 is not below 25. Q9 points to the evidence file of the issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>


Mark T7 done, add the T7 evidence section (test, mutation proofs, documents changed, Compose review, ADRs) and a progress-log entry. Restore the Failures and Follow-up heading that the T5 documentation commit deleted by mistake.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cessor

Shutdown gains deadline, http_drain, udp_request_drain and event_listener_drain, each returning the budget as a Duration, so a consumer no longer spells out the key and the conversion at 31 call sites. The accessor test uses four different values, so an accessor that returns another budget fails it; swapping two accessors was seen to fail it. The wiring was re-proved at the executable boundary on the new path: passing the default HTTP drain, or a fixed cleanup deadline, still fails the native-binary tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…2668

Add the D5 follow-up evidence section (five findings, one change, the re-proof on the new path) and a progress-log entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add V2 to V8: the real binary and the release image with SIGTERM and SIGINT (defaults, drain budget reached, invalid configurations, the V1 recheck with the temporary fault-injection patch, Docker with a 35 s and the default 10 s stop timeout, systemd). Podman was not run. Mark T8 and the manual-verification checkpoints done.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>


V2 to V8 used evidence/hold-and-stop.sh and evidence/baseline.toml, so they are removed as the specification planned. The held-connection exit codes are covered by the lifecycle-shutdown-policy target; the fault-injection, Docker and systemd runs stay recorded in manual-verification-evidence.md. The evidence file no longer links the removed folder.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… literal

The T4 commit dropped the comment and the blank line that separate the configuration fields from the registar in the AppContainer struct literal. The independent review of torrust#2668 found it; no behavior changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e entry-point guide

src/AGENTS.md said main waits up to 10 seconds. It now names application.shutdown.deadline_in_secs and the exit-code rule. The shutdown feature README gets its last-updated stamp bumped to the time of its last content change for torrust#2668.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… for torrust#2668

Add agent-review-reports.md with the first independent review (REVIEW FAILED for documentation findings), the V9 real-binary check of the UDP and HTTP-family drains, and implementation-retrospective.md. Fix the stale evidence text, the M3 expectation, the test counts and the Rollback text, update the completion-review lines, and tick the two completed workflow checkpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…st#2668

Append the re-review entry (REVIEW PASSED) to agent-review-reports.md, add the final test count to the T2 evidence, mark T9 done, and log the final state before the pull request. No acceptance criterion is ticked.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t#2668

Set related-pr to 2678 and add a progress-log entry: the branch was rebased onto the new develop because another merged pull request conflicted with the shared native-tracker fixture, and the gate was rerun on the rebased tree.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only the HTTP-family drains (HTTP tracker, REST API, health check API) end as a failed component when their budget is reached. The UDP request drain and the event listener drain log a warning and still end as completed or cancelled, so exit code 0 does not prove that no UDP request or queued event was discarded. The budget ADR and the exit code section of docs/containers.md said that any drain reaching its budget fails the stop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…it-result section

The exit-result table said any outcome including a drain budget reached is a failure. Only the HTTP-family drains are; the UDP request drain and the listener drain only log a warning. Add a progress-log entry for the review of pull request 2678.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
One Copilot finding (F1, documentation): which drain timeouts end a stop with exit code 1. It was fixed in the commit that qualifies them, replied to, and resolved; the validator exits 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion container

The seven event listener sites now take the drain budget from app_container.application_config.shutdown.event_listener_drain(), the single source of the other budgets, and the event_listener_drain_timeout(config) helper is removed. The HTTP core, UDP core and UDP server statistics starters drop the config parameter they only used for the helper (it was an unused _config on develop), and the UDP banning starter returns to its develop signature. The torrent repository and tracker core starters keep config for core settings. The wrappers in src/app.rs lose the parameter too. The helper test is removed: its behavior is covered by the accessor test of the configuration package and the AppContainer test, and the source-level guard now counts the container call per listener site; it fails when a site uses a hard-coded 5 s or another budget.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pplication container

The three starter tests that already build an AppContainer (HTTP tracker TLS and listener errors, UDP tracker socket release) read the drain value from app_container.application_config like the composition root. The starters themselves take a Duration and read no configuration. The remaining tests have no container in scope and keep the configuration or the default. No behavior changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… commit that touched it

The stamp read 2026-10-27 14:59, the time of the last content change, while a later commit changed the file again. It now carries the time of this review round. Every other changed file with a last-updated-utc was re-checked against the author time of the last commit that touched it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ers for torrust#2668

Describe the single source (the application container) in the D5 text and the T4 row, add the evidence section for the listener drain change (decision, answer to the maintainer's question, tests, two mutation proofs), mark finding 5 of the D5 follow-up as superseded, restore the Failures and Follow-up heading that the D5 edit had dropped, and add the progress-log entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@josecelano
josecelano force-pushed the 2668-configure-shutdown-policy branch from 773678c to a6457bf Compare October 28, 2026 13:31
@josecelano

Copy link
Copy Markdown
Member Author

ACK a6457bf

@josecelano
josecelano merged commit fb278a6 into torrust:develop Oct 28, 2026
29 checks passed

This branch was successfully deployed

1 active deployment
coverage — a6457bf8 Deployed Oct 28, 2026 by josecelano via Generate Coverage Report #2851
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configure shutdown policy and deployment contract

2 participants