{"record":{"id":"54eb4d2fa1216f5f","repo":"risingwavelabs/risingwave","slug":"unprocessed-shared-node","errorCode":null,"errorMessage":"Unprocessed shared node.","messagePattern":"Unprocessed shared node\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/frontend/src/optimizer/plan_node/merge_eq_nodes.rs","lineNumber":148,"sourceCode":"            .expect(\"dag cache is only used for shares\")\n            .share_id();\n        self.cache.get(&share_id).cloned().unwrap_or_else(|| {\n            let res = f(self);\n            self.cache.entry(share_id).or_insert(res).clone()\n        })\n    }\n}\n\nimpl Endo<PlanRef> for Pruner<'_> {\n    fn pre(&mut self, t: PlanRef) -> PlanRef {\n        let prunable = |s: &&LogicalShare| {\n            // Prune if share node has only one parent\n            // or it just shares a scan\n            // or it doesn't share any scan or source.\n            *self\n                .counts\n                .get(&s.share_id())\n                .expect(\"Unprocessed shared node.\")\n                == 1\n                || s.input().as_logical_scan().is_some()\n                || !(plan_visitor::has_logical_scan(s.input())\n                    || plan_visitor::has_logical_source(s.input()))\n        };\n        t.as_logical_share()\n            .filter(prunable)\n            .map_or(t.clone(), |s| self.pre(s.input()))\n    }\n\n    fn apply(&mut self, t: PlanRef) -> PlanRef {\n        self.dag_apply(t)\n    }\n}\n","sourceCodeStart":130,"sourceCodeEnd":163,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/optimizer/plan_node/merge_eq_nodes.rs#L130-L163","documentation":"`pre` in MergeEqNodes prunes share nodes that have only one parent (or that share just a scan / no scan or source). Before counting, it looks up the share's share_id in the precomputed `counts` map with `expect(\"Unprocessed shared node.\")`. The panic means the DAG traversal reached a share node whose refcount was never recorded, i.e. the counting pre-pass did not visit this share before `pre` consumed it.","triggerScenarios":"Running `pre` (from dag_apply) on a plan whose share refcount map was built for a different/older plan; a share node added after the counting pass; inconsistent plan identity between the counting traversal and the rewrite traversal.","commonSituations":"Custom optimizer rules that create new LogicalShare nodes after the count pre-pass; mutating the plan between counting and rewriting; rebasing or upgrading RisingWave and touching optimizer passes so the share counting step is skipped or reordered.","solutions":["Make sure the share-counting pass runs on the exact same plan instance immediately before dag_apply/pre.","Verify any custom rules that create LogicalShare nodes run before (or re-run) the counting pass.","Add the new share_id into `counts` whenever a share node is constructed, or rebuild counts after plan mutation.","Log the missing share_id at the call site to identify which rule produced the uncounted share."],"exampleFix":"// before\nlet count = *self.counts.get(&s.share_id()).expect(\"Unprocessed shared node.\");\n// after\nlet count = match self.counts.get(&s.share_id()) {\n    Some(c) => *c,\n    None => {\n        // share was not seen during counting; rebuild counts or treat as single-parent\n        1\n    }\n};","handlingStrategy":"validation","validationCode":"// Rust: verify counts map coverage before rewriting\ndebug_assert!(self.counts.contains_key(&share.share_id()), \"share id {} missing from counts\", share.share_id());","typeGuard":"fn counted(counts: &HashMap<ShareId, usize>, id: ShareId) -> Option<usize> { counts.get(&id).copied() }","tryCatchPattern":"// No exceptions in Rust; use Option fallback instead of expect.\nlet count = counted(&self.counts, s.share_id()).unwrap_or(1);","preventionTips":["Rebuild share counts immediately before every DAG rewrite.","Never create LogicalShare nodes after the counting pass without re-counting.","Add tests covering plans with shares introduced mid-pipeline."],"tags":["internal-invariant","optimizer","rust","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}