Parent: #1051
Today
packages/zaino-state/src/chain_index/mempool.rs runs a sync task spawned at :144. The loop body has multiple tokio::time::sleep(100ms / 500ms) calls and a single status.load() == Closing check at :228 near the bottom. close() (:335) stores Closing and calls handle.abort(). Drop (:347) does the same.
Failure modes:
- Latency: shutdown takes anywhere from one full sleep cycle (100 ms minimum, up to 500 ms in the recoverable-error path) before the task observes
Closing.
- Lost graceful drain:
abort() kills the task mid-sleep, so the trailing state.notify(Closing) at :229–230 is skipped. Subscribers never see the explicit closed-state notification.
Proposed change
- Add
cancel_token: CancellationToken to Mempool; clone it into the spawned sync task.
- Wrap each
tokio::time::sleep(...) call in the sync-task body as:
tokio::select! {
_ = tokio::time::sleep(Duration::from_millis(N)) => {},
_ = token.cancelled() => return,
}
- The
if status.load() == StatusType::Closing check at :228 collapses into the cancellation arm.
close(): self.cancel_token.cancel(); await handle; — the task exits via the cancelled arm and runs state.notify(Closing) cleanly.
Drop keeps abort() as a backstop (we may not be in an async context at drop time).
Acceptance criteria
- All
tokio::time::sleep(...) calls in the sync-task body are inside a select with cancel_token.cancelled().
close() no longer calls abort() on the success path; the task exits gracefully and the state.notify(Closing) line runs.
- New unit test asserts
close() completes within ~20 ms.
- Existing mempool tests remain green.
Out of scope
Parent: #1051
Today
packages/zaino-state/src/chain_index/mempool.rsruns a sync task spawned at:144. The loop body has multipletokio::time::sleep(100ms / 500ms)calls and a singlestatus.load() == Closingcheck at:228near the bottom.close()(:335) storesClosingand callshandle.abort().Drop(:347) does the same.Failure modes:
Closing.abort()kills the task mid-sleep, so the trailingstate.notify(Closing)at:229–230is skipped. Subscribers never see the explicit closed-state notification.Proposed change
cancel_token: CancellationTokentoMempool; clone it into the spawned sync task.tokio::time::sleep(...)call in the sync-task body as:if status.load() == StatusType::Closingcheck at:228collapses into the cancellation arm.close():self.cancel_token.cancel(); await handle;— the task exits via the cancelled arm and runsstate.notify(Closing)cleanly.Dropkeepsabort()as a backstop (we may not be in an async context at drop time).Acceptance criteria
tokio::time::sleep(...)calls in the sync-task body are inside a select withcancel_token.cancelled().close()no longer callsabort()on the success path; the task exits gracefully and thestate.notify(Closing)line runs.close()completes within ~20 ms.Out of scope
Mempoolowning its ownCancellationToken; child-token wiring follows when zainod owns the root.