{"record":{"id":"d6d45e989929a28b","repo":"libnyanpasu/clash-nyanpasu","slug":"semaphore-should-never-closed","errorCode":null,"errorMessage":"semaphore should never closed","messagePattern":"semaphore should never closed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"backend/nyanpasu-core/src/state/coordinator.rs","lineNumber":111,"sourceCode":"        let actual = self.snapshot_versioned().version;\n        self.next_change_id = StateChangeId(actual.next());\n        actual\n    }\n\n    fn clone_subscribers(&self) -> Subscribers<T> {\n        self.subscribers.values().cloned().collect()\n    }\n\n    pub async fn upsert(\n        &mut self,\n        builder: impl StateAsyncBuilder<State = T>,\n    ) -> Result<PrepareReport, StateChangedError> {\n        let permit = self\n            .semaphore\n            .clone()\n            .acquire_owned()\n            .await\n            .expect(\"semaphore should never closed\");\n        let subscribers = self.clone_subscribers();\n        let notify_strategy = self.notify_strategy;\n        let new_state = builder\n            .build()\n            .await\n            .map_err(StateChangedError::Validation)?;\n        let next_changed_id = self.pending_change_id();\n        let current_state = self.snapshot_versioned();\n        let change = StateChange {\n            id: next_changed_id,\n            previous: Some(current_state.clone()),\n            current: Arc::new(new_state),\n        };\n        let tx = new_transaction(\n            change,\n            self.current_state.clone(),\n            subscribers,\n            notify_strategy,","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/nyanpasu-core/src/state/coordinator.rs#L93-L129","documentation":"Coordinator::upsert acquires an owned permit from an internal semaphore before building new state. The semaphore is created with enough permits and is never closed by the coordinator, so acquire can only fail if the semaphore was closed — an impossible condition by design. The code uses .expect to panic loudly if that internal invariant is ever broken.","triggerScenarios":"Calling coordinator.upsert(...) after the coordinator's internal semaphore has been closed (only possible via misuse, unsafe introspection, or a bug in coordinator lifecycle/teardown).","commonSituations":"Practically never hit in production; seen only when custom code closes/leaks the coordinator's semaphore, or after a partially-torn-down coordinator is reused (e.g. in tests dropping and rebuilding the actor graph incorrectly).","solutions":["Do not close the coordinator's semaphore; treat it as owned exclusively by the coordinator.","Verify you are not holding a Coordinator built from a torn-down or dropped actor state; construct it via the composition root / builder instead.","If this panic reproduces, file a bug with the coordinator construction and teardown sequence — it indicates an internal invariant violation.","As a temporary guard, wrap upsert calls so a panic is caught and the coordinator is rebuilt from scratch."],"exampleFix":"// before: reusing a coordinator after closing its semaphore\nsemaphore.close();\ncoordinator.upsert(builder).await?;\n\n// after: never close the semaphore; use a fresh coordinator\nlet coordinator = StateCoordinator::new(deps); // semaphore created internally\ncoordinator.upsert(builder).await?;","handlingStrategy":"try-catch","validationCode":"// Not preventable by pre-validation; ensure coordinator is alive.\nassert!(!coordinator_is_shutdown(), \"coordinator already torn down\");","typeGuard":null,"tryCatchPattern":"match std::panic::catch_unwind(AssertUnwindSafe(|| coordinator.upsert(builder))) { ... }","preventionTips":["Never close the coordinator's internal semaphore.","Obtain the coordinator only from the composition root / builder.","Don't reuse coordinator handles after actor shutdown.","Treat any occurrence as a bug report."],"tags":["panic","semaphore","internal-invariant","rust"],"backgroundTag":"internal-invariant-violation","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}