{"record":{"id":"4b027027da6bc79e","repo":"embassy-rs/embassy","slug":"zerocopypubsub-cannot-have-multiple-subscribers","errorCode":null,"errorMessage":"ZeroCopyPubSub cannot have multiple subscribers ","messagePattern":"ZeroCopyPubSub cannot have multiple subscribers ","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"embassy-stm32-wpan/src/net/util.rs","lineNumber":42,"sourceCode":"    pub fn publish(&self, event: B) {\n        if let Some(signal) = self.event.borrow().borrow_mut().as_ref() {\n            signal.signal(event);\n        }\n    }\n\n    pub fn subscribe<'a>(&'a self) -> Subscriber<'a, B> {\n        Subscriber::new(&self.event)\n    }\n}\n\npub struct Subscriber<'a, B: ControllerToHostPacketBox> {\n    event: &'a blocking_mutex::Mutex<NoopRawMutex, RefCell<Option<Signal<NoopRawMutex, B>>>>,\n}\n\nimpl<'a, B: ControllerToHostPacketBox> Subscriber<'a, B> {\n    fn new(event: &'a blocking_mutex::Mutex<NoopRawMutex, RefCell<Option<Signal<NoopRawMutex, B>>>>) -> Self {\n        if event.borrow().borrow_mut().replace(Signal::new()).is_some() {\n            panic!(\"ZeroCopyPubSub cannot have multiple subscribers \")\n        }\n\n        Self { event }\n    }\n\n    pub async fn wait(&self) -> B {\n        poll_fn(|cx| self.event.borrow().borrow_mut().as_ref().unwrap().wait().poll_unpin(cx)).await\n    }\n}\n\nimpl<'a, B: ControllerToHostPacketBox> Drop for Subscriber<'a, B> {\n    fn drop(&mut self) {\n        self.event.borrow().borrow_mut().take();\n    }\n}\n","sourceCodeStart":24,"sourceCodeEnd":58,"githubUrl":"https://github.com/embassy-rs/embassy/blob/463a07b963419a1bfe61d5d597c44acb810afb8b/embassy-stm32-wpan/src/net/util.rs#L24-L58","documentation":"ZeroCopyPubSub in embassy-stm32-wpan's net layer is implemented as a single-slot publisher/subscriber: it stores one Option<Signal> behind a mutex and replaces it on subscribe. If a signal is already stored, a second Subscriber::new call panics because the design supports at most one subscriber.","triggerScenarios":"Calling Subscriber::new (via the pub-sub API, e.g. obtaining a subscriber from ZeroCopyPubSub) more than once for the same channel while the previous subscriber still exists.","commonSituations":"Constructing two subscribers in different parts of the app (e.g. one for BLE, one for logging); spawning a second task that subscribes after an earlier task already did; retry logic that re-creates a subscriber without dropping the old one.","solutions":["Create the subscriber exactly once and share it (or pass it) to all consumers","Drop the existing subscriber before creating a new one","Restructure to use a real multi-subscriber pub-sub crate (e.g. embassy-sync pubsub channel) if multiple consumers are required"],"exampleFix":"// before\nlet sub1 = pubsub.subscriber();\nlet sub2 = pubsub.subscriber(); // panics\n// after\nlet sub1 = pubsub.subscriber();\n// pass sub1 to every task that needs it, or drop(sub1) before re-subscribing","handlingStrategy":"validation","validationCode":"// create the subscriber once, e.g. in a static/OnceCell\nstatic SUB: OnceCell<Subscriber> = OnceCell::new();\nif SUB.get().is_none() { let _ = SUB.set(pubsub.subscriber()); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Construct subscribers in a single init location","Never re-create a subscriber on retry without dropping the old one","Use embassy-sync pubsub channel if you need multiple subscribers"],"tags":["embedded","panic","pubsub","stm32"],"backgroundTag":"invalid-state-transition","analyzedSha":"463a07b963419a1bfe61d5d597c44acb810afb8b","analyzedAt":"2026-09-10T13:38:26.660Z","contentChangedAt":"2026-09-10T13:38:26.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}