{"record":{"id":"4f76c25264f51978","repo":"vectordotdev/vector","slug":"tasks-completed-wait-group-already-consumed","errorCode":null,"errorMessage":"tasks completed wait group already consumed","messagePattern":"tasks completed wait group already consumed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/components/validation/sync.rs","lineNumber":263,"sourceCode":"    pub async fn shutdown(&mut self) {\n        info!(\"{}: triggering task to shutdown.\", self.name);\n\n        // Trigger all registered shutdown handles.\n        for trigger in self.state.shutdown_triggers.drain(..) {\n            trigger.trigger();\n            debug!(\"{}: shutdown triggered for coordinated tasks.\", self.name);\n        }\n\n        // Now simply wait for all of them to mark themselves as completed.\n        debug!(\n            \"{}: waiting for coordinated tasks to complete...\",\n            self.name\n        );\n        let tasks_completed = self\n            .state\n            .tasks_completed\n            .as_mut()\n            .expect(\"tasks completed wait group already consumed\");\n        tasks_completed.wait_for_children().await;\n\n        info!(\"{}: task has been shutdown.\", self.name);\n    }\n}\n","sourceCodeStart":245,"sourceCodeEnd":269,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/src/components/validation/sync.rs#L245-L269","documentation":"During validation shutdown, the component asserts that its `tasks_completed` wait group is still present (`Some`). It is a `take()`-style option: once shutdown runs a second time the wait group has been consumed, and `expect` panics with \"tasks completed wait group already consumed\". This is an internal invariant guard against double-shutdown of a validated task.","triggerScenarios":"Calling `shutdown()` twice on the same validated component/task in src/components/validation/sync.rs, or otherwise consuming `state.tasks_completed` (via `take()`) before `shutdown()` reads it.","commonSituations":"Bugs in validation harness code that run the shutdown path twice, component re-registration where the old task object is shut down again, or custom test drivers that tear down tasks manually and then call the public shutdown API.","solutions":["Ensure shutdown() is invoked exactly once per task; guard the call site with an idempotent flag or OnceCell.","Check for any code path that calls `state.tasks_completed.take()` before shutdown runs and remove it.","If a double shutdown is legitimately possible in your driver, recreate the task instead of reusing it."],"exampleFix":"// before\ntask.shutdown().await;\ntask.shutdown().await; // panics: wait group already consumed\n\n// after\ntask.shutdown().await;\n// do not call shutdown twice; drop the task instead","handlingStrategy":"try-catch","validationCode":"if task.is_shutdown() { return; }  // or track a `shutdown_done: bool` before calling","typeGuard":"fn can_shutdown(task: &TaskHandle) -> bool { !task.shutdown_started() }","tryCatchPattern":"// Rust panics are not catchable by callers of the API; instead make the call idempotent:\nlet mut did_shutdown = false;\nif !did_shutdown { handle.shutdown().await; did_shutdown = true; }","preventionTips":["Treat shutdown as a one-shot operation; wrap it in a guard type that prevents double invocation.","Never manually consume state fields (`take()`) that shutdown owns.","In tests, drop task handles after shutdown instead of reusing them."],"tags":["async","shutdown","invariant-violation"],"backgroundTag":"invalid-state-transition","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}