paritytech / paritytech/web3-storage
[Provider] Graceful shutdown & ordered startup (signal handling, drain, crash-safe persistence)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
Subtask of #254: the restart mechanics needed for low-downtime provider upgrades. This covers clean shutdown and startup only; self-update and config hot-reload stay in #254.
Motivation
A provider restart is unavoidable (binary upgrades, host maintenance, crashes) — and the protocol tolerates it, but on two very different clocks: challenges leave a 48h response window, while provider-initiated checkpoints run on ~10-minute windows with only a ~2-minute grace (DefaultCheckpointInterval = 100 blocks, DefaultCheckpointGrace = 20) — a restart that overruns the grace while the provider is checkpoint leader for a bucket can already be hit with report_missed_checkpoint → CheckpointMissPenalty. So downtime must be clean and bounded against the checkpoint grace, not the challenge window: today the provider cannot be stopped without aborting in-flight work, and it cannot tell an orchestrator (systemd, k8s, a future self-update path in #254) when it is actually ready or actually done.
Every low-downtime upgrade story in #254 bottoms out on this primitive: stop accepting work → finish/flush what's in flight → exit with intent → come back up in a known-good order. It doesn't exist yet.
Current state (evidence)
- No signal handling at all. The HTTP server is started as plain
axum::serve(listener, app).await(provider-node/src/command.rs:130-134) with no.with_graceful_shutdown(...); there is notokio::signal/ SIGTERM / SIGINT handler anywhere inprovider-node. Akilldrops in-flight uploads, commits, and challenge responses mid-flight. - Coordinator lifecycle is manual and unwired.
ChainStateCoordinator/CheckpointCoordinator/ReplicaSyncCoordinator/ChallengeRespondereach have a*Handle::stop()that just aborts the task (e.g.chain_state_coordinator.rs:427-435), but nothing calls them on shutdown — tasks are dropped abruptly when the runtime exits. - Persistence is clean-shutdown-only. RocksDB relies on flush-on-drop ("normal shutdown" comment at
storage/disk.rs:573) — not crash-safe; the fs/s3 index managers persist JSON snapshots that can be torn on abrupt exit. The nonce high-water mark (DiskNonceStore, recovered viamax(persisted, hsn+1)inchain_state_coordinator.rs:316-337) is the one piece that is restart-aware by design. - Startup has no ordering or readiness gate. Storage open, chain connect, multiaddr sync, coordinator spawn, and HTTP bind happen without a readiness distinction —
/healthreports liveness before the chain state is reconciled, so an orchestrator can route traffic (or a supervisor can consider the upgrade "done") while the provider is still bootstrapping.
Proposed scope
1. Shutdown
- Install SIGTERM/SIGINT handlers; wire them into
axum::serve(...).with_graceful_shutdown(...). - Define and implement a drain sequence with a configurable overall deadline (e.g.
--shutdown-grace-secs, default ~30s — the full stop→start cycle should fit comfortably insideDefaultCheckpointGraceto avoid missed-checkpoint penalties):- flip to draining: reject new mutating requests (uploads, commits, negotiate) with
503 + Retry-After; keep serving reads; - let in-flight requests complete (bounded by the deadline);
- stop coordinators in dependency order — challenge responder last, and let it finish an already-started challenge response (that's stake at risk);
- flush storage: RocksDB flush + WAL sync, fs/s3 index snapshots written atomically (write-temp + rename), persist nonce HWM;
- exit 0 on a clean drain, distinct non-zero code on deadline-forced exit.
- flip to draining: reject new mutating requests (uploads, commits, negotiate) with
- Make index snapshot writes atomic regardless of shutdown (crash-safety, not just SIGTERM-safety).
2. Startup
- Ordered bootstrap: open storage (+ cheap integrity check of index vs DB) → connect chain → reconcile provider state (constants,
ProviderInfo, hsn/nonce) → sync multiaddr → start coordinators → then report ready. - Split readiness from liveness: keep
/healthas liveness; add/readythat only turns true after chain reconciliation completes. Document both for operators. - Log a single structured "startup summary" line (chain, spec_version, provider account, buckets served, recovery actions taken) — feeds #214.
3. Integration test
- Start provider → begin an upload + a pending challenge → SIGTERM → assert clean drain, restart, assert challenge still answered and no data loss.
Related
- #254 — parent (low-downtime binary upgrades)
- #222 — chain connection / reconnect behaviour on the other side of a restart
- #214 — readiness/liveness + startup metrics
- #178 — the drain logic should land in whatever crate split owns the coordinators
- #149 — audit item P11 ("no graceful shutdown handler in provider node") is superseded by this issue
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with provider-node/src/command.rs:130-134 and the coordinator stop handles, then inspect storage/disk.rs:573 and the nonce recovery logic in chain_state_coordinator.rs:316-337. Trace how HTTP serving, coordinator tasks, and index persistence currently start and stop. Done means bounded signal-driven draining, ordered bootstrap with separate liveness/readiness, crash-safe persistence, and the described restart integration test passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, devops, distributed-systems, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100