fix: ExplainNode guard against NULL planstate on QE - #1865
Conversation
There was a problem hiding this comment.
Hi, @roaldm153 welcome!🎊 Thanks for taking the effort to make our project better! 🙌 Keep making such awesome contributions!
There was a problem hiding this comment.
Pull request overview
Fixes a QE-side crash in ExplainNode() when a plan subtree lives in a different slice and the corresponding PlanState * is NULL, by adding a defensive NULL guard and avoiding recursive descent into a NULL outer child.
Changes:
- Add a
planstate == NULLearly return before dereferencingplanstate->plan. - Move
plan = planstate->planassignment after the newNULLguard. - Guard recursion into
outerPlanState(planstate)to avoid callingExplainNode()with aNULLchild.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
LGTM. I think you should rephrase the commit message. Maybe like that As far as I see this code is unreachable in-tree. The only callers of ExplainPrintPlan() are ExplainOnePlan(), which runs on the QD (for example, in auto_explain - it returns from _PG_init when Gp_role != GP_ROLE_DISPATCH (contrib/auto_explain/auto_explain.c:101)) - so it never installs its hooks on a segment. Only an out-of-tree extension loaded on the segments can hit this path or debuggers today. That's fine as a hardening fix, but I think you should state it explicitly in the commit message. |
A receiving Motion has no child PlanState when its child slice runs on another gang. ExplainNode() recursed into it anyway and crashed. Return early when planstate is NULL.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
905983f to
d3b5e84
Compare
| */ | ||
| /* | ||
| * Guard against the case where a subtree on the QE lives in another slice | ||
| * and is not instantiated in this slice. |
There was a problem hiding this comment.
The comment doesn't say where the decision not to instantiate the subtree is made, so a reader has no starting point. I think you should add a pointer to ExecInitMotion here:
/*
* Guard against the case where a subtree on the QE lives in another slice
* and is not instantiated in this slice (see ExecInitMotion).
*/
If subtree on QE lives in another slice there is not guards and it crashes with segfault, so I fixed it
Fixes #ISSUE_Number
What does this PR do?
This change fixes a crash that occurs on the Query Executor (QE) when a subtree lives in a different slice.
The original code assumed that planstate is always non‑NULL and directly dereferenced planstate->plan.
When the subtree is not instantiated in the current slice, planstate can be NULL, leading to a segmentation fault.
The patch adds:
A NULL guard for planstate at the start of ExplainNode.
Moves the assignment plan = planstate->plan after the guard.
Additional guard when recursing into the outer plan to avoid calling ExplainNode with a NULL outerPlanState.
These changes make EXPLAIN robust across slice boundaries without altering any existing functionality.
Type of Change
Breaking Changes
No breaking changes. The public API and plan execution behavior remain unchanged; only additional safety checks were added.
Test Plan
Unit tests: No new unit tests were added because the fix is defensive and does not change the logical output of EXPLAIN.
Integration tests: Ran the full regression test suite, including parallel tests, and verified that the previously failing scenario no longer crashes.
make installcheckmake -C src/test installcheck-cbdb-parallelImpact
Performance:
Negligible impact – the added NULL checks are inexpensive branch predictions and do not affect the typical execution path.
User-facing changes:
ExplainPrintPlan now works on QE
Dependencies:
Nope
Checklist
Additional Context
ExplainNode was called from the QE side with a planstate that had not been instantiated because the subtree resides entirely in another slice.