{"record":{"id":"240a222ad505e829","repo":"LemmyNet/lemmy","slug":"cancellabletask-aborted-due-to-shutdown-timeout","errorCode":null,"errorMessage":"CancellableTask aborted due to shutdown timeout","messagePattern":"CancellableTask aborted due to shutdown timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/apub/send/src/util.rs","lineNumber":120,"sourceCode":"        if stop2.is_cancelled() {\n          return;\n        } else {\n          tracing::warn!(\"task exited, restarting: {res:?}\");\n        }\n      }\n    });\n    let abort = task.abort_handle();\n    CancellableTask {\n      f: Box::pin(async move {\n        stop.cancel();\n        tokio::select! {\n            r = task => {\n              r.context(\"CancellableTask failed to cancel cleanly, returned error\")?;\n              Ok(())\n            },\n            _ = sleep(timeout) => {\n                abort.abort();\n                Err(anyhow!(\"CancellableTask aborted due to shutdown timeout\"))\n            }\n        }\n      }),\n    }\n  }\n\n  /// cancel the cancel signal, wait for timeout for the task to stop gracefully, otherwise abort it\n  pub async fn cancel(self) -> Result<(), anyhow::Error> {\n    self.f.await\n  }\n}\n\n/// assuming apub priv key and ids are immutable, then we don't need to have TTL\n/// TODO: capacity should be configurable maybe based on memory use\npub(crate) async fn get_actor_cached(\n  pool: &mut DbPool<'_>,\n  actor_type: ActorType,\n  actor_apub_id: &Url,","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/LemmyNet/lemmy/blob/439734dd638a2c06a2f907beab7dcf4646e88f86/crates/apub/send/src/util.rs#L102-L138","documentation":"CancellableTask wraps an async task with a cancellation future; this error is returned when the task did not finish after the shutdown/cancel signal within the given timeout, so the underlying tokio task was aborted. It signals a graceful-shutdown failure rather than a task-level business error.","triggerScenarios":"Shutting down the federation worker/send loop while the inner task is still running and does not react to cancellation (or is blocked on a slow I/O call) longer than the configured timeout duration.","commonSituations":"Instance restart/redeploy while federation tasks are mid-flight; long blocking DB or HTTP calls inside the wrapped task; a bug in the task that ignores the cancellation token; timeout configured too aggressively low.","solutions":["Increase the shutdown timeout passed to CancellableTask so slow tasks have time to finish","Make the wrapped task respond promptly to the abort/cancellation signal (avoid long non-cancellable awaits)","Check why the task hung — e.g. stuck DB connections or network calls without timeouts","Retry/inspect at restart; the task was force-aborted so any in-progress work was dropped"],"exampleFix":"// before\nCancellableTask::new(task, Duration::from_secs(1))\n// after\nCancellableTask::new(task, Duration::from_secs(30)) // allow graceful drain","handlingStrategy":"retry","validationCode":"// ensure the task is cancellation-aware before wrapping\n// e.g. select! on a shutdown token inside the task loop","typeGuard":null,"tryCatchPattern":"match cancellable_task.join().await {\n  Err(e) if e.to_string().contains(\"aborted due to shutdown timeout\") => {\n    warn!(\"graceful shutdown timed out; task was force-aborted\");\n    // retry or continue shutdown\n  }\n  other => other?,\n}","preventionTips":["Set a shutdown timeout larger than your task's worst-case step","Use cancellation tokens inside long loops and check them per iteration","Avoid unbounded blocking I/O inside cancellable tasks","Test shutdown under load before deploying"],"tags":["shutdown","timeout","tokio","cancellation"],"backgroundTag":"request-timeout","analyzedSha":"439734dd638a2c06a2f907beab7dcf4646e88f86","analyzedAt":"2026-09-06T11:28:35.035Z","contentChangedAt":"2026-09-06T11:28:35.035Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}