Skip to content

fix: ExplainNode guard against NULL planstate on QE - #1865

Open
roaldm153 wants to merge 2 commits into
apache:REL_2_STABLEfrom
roaldm153:explain-node-fix
Open

fix: ExplainNode guard against NULL planstate on QE#1865
roaldm153 wants to merge 2 commits into
apache:REL_2_STABLEfrom
roaldm153:explain-node-fix

Conversation

@roaldm153

Copy link
Copy Markdown

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

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

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.

  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

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.

@roaldm153 roaldm153 changed the title fix: ExplainNode guard against NULL planstate when subtree lives in another slice fix: ExplainNode guard against NULL planstate on QE Jul 27, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @roaldm153 welcome!🎊 Thanks for taking the effort to make our project better! 🙌 Keep making such awesome contributions!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 == NULL early return before dereferencing planstate->plan.
  • Move plan = planstate->plan assignment after the new NULL guard.
  • Guard recursion into outerPlanState(planstate) to avoid calling ExplainNode() with a NULL child.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/backend/commands/explain.c Outdated
@Alena0704

Copy link
Copy Markdown
Contributor

LGTM. I think you should rephrase the commit message. Maybe like that

Fix segfault in ExplainNode() on a QE

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.

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.

roaldm153 and others added 2 commits July 28, 2026 10:43
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>
*/
/*
* Guard against the case where a subtree on the QE lives in another slice
* and is not instantiated in this slice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).
 */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants