From 1f47bcef6593b5e0d46591bf1b78ef849c269ba6 Mon Sep 17 00:00:00 2001 From: Shihao Date: Fri, 18 Sep 2026 22:44:38 -0400 Subject: [PATCH v1 1/2] Pass the current tuple bound to workers on Gather rescan Commit 3452dc5240d made Gather and Gather Merge pass the tuple bound to the workers through the DSM segment. But the bound was only stored by ExecInitParallelPlan(). ExecParallelReinitialize() reuses the segment and left the old value in place. So after a rescan the new workers still used the bound from the first scan. If a Limit above the Gather depends on a parameter, a later scan can need more tuples than the first one. The workers then stopped too early, and the query returned too few rows. With Gather Merge over a Sort it could return the right number of wrong rows. Fix by adding a tuples_needed argument to ExecParallelReinitialize() and storing it in the shared state, like ExecInitParallelPlan() does. Reported-by: Jeff Davis Discussion: https://postgr.es/m/2701c83e33f3fe66320291fb5d1b74ca2cb90bd7.camel@j-davis.com Backpatch-through: 14 --- src/backend/executor/execParallel.c | 10 +++++++++- src/backend/executor/nodeGather.c | 3 ++- src/backend/executor/nodeGatherMerge.c | 3 ++- src/include/executor/execParallel.h | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 6b85508a696..b7675409cd1 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -967,7 +967,8 @@ ExecParallelCreateReaders(ParallelExecutorInfo *pei) void ExecParallelReinitialize(PlanState *planstate, ParallelExecutorInfo *pei, - Bitmapset *sendParams) + Bitmapset *sendParams, + int64 tuples_needed) { EState *estate = planstate->state; FixedParallelExecutorState *fpes; @@ -989,6 +990,13 @@ ExecParallelReinitialize(PlanState *planstate, fpes = shm_toc_lookup(pei->pcxt->toc, PARALLEL_KEY_EXECUTOR_FIXED, false); + /* + * The tuple bound might have changed since the last scan, for instance if + * a Limit node above us depends on a parameter. Pass the current value + * to the new batch of workers. + */ + fpes->tuples_needed = tuples_needed; + /* Free any serialized parameters from the last round. */ if (DsaPointerIsValid(fpes->param_exec)) { diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c index 31cd95adee7..bef118dea83 100644 --- a/src/backend/executor/nodeGather.c +++ b/src/backend/executor/nodeGather.c @@ -172,7 +172,8 @@ ExecGather(PlanState *pstate) else ExecParallelReinitialize(outerPlanState(node), node->pei, - gather->initParam); + gather->initParam, + node->tuples_needed); /* * Register backend workers. We might not get as many as we diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 352a89ab53f..c96a333cd72 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -216,7 +216,8 @@ ExecGatherMerge(PlanState *pstate) else ExecParallelReinitialize(outerPlanState(node), node->pei, - gm->initParam); + gm->initParam, + node->tuples_needed); /* Try to launch workers. */ pcxt = node->pei->pcxt; diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h index 5a2034811d5..4ab902d5e12 100644 --- a/src/include/executor/execParallel.h +++ b/src/include/executor/execParallel.h @@ -44,7 +44,8 @@ extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei); extern void ExecParallelFinish(ParallelExecutorInfo *pei); extern void ExecParallelCleanup(ParallelExecutorInfo *pei); extern void ExecParallelReinitialize(PlanState *planstate, - ParallelExecutorInfo *pei, Bitmapset *sendParams); + ParallelExecutorInfo *pei, Bitmapset *sendParams, + int64 tuples_needed); extern void ParallelQueryMain(dsm_segment *seg, shm_toc *toc); -- 2.37.1 (Apple Git-137.1)