1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-03 20:02:46 +03:00

Fix planner crash from pfree'ing a partial path that a GatherPath uses.

We mustn't run generate_gather_paths() during add_paths_to_joinrel(),
because that function can be invoked multiple times for the same target
joinrel.  Not only is it wasteful to build GatherPaths repeatedly, but
a later add_partial_path() could delete the partial path that a previously
created GatherPath depends on.  Instead establish the convention that we
do generate_gather_paths() for a rel only just before set_cheapest().

The code was accidentally not broken for baserels, because as of today there
never is more than one partial path for a baserel.  But that assumption
obviously has a pretty short half-life, so move the generate_gather_paths()
calls for those cases as well.

Also add some generic comments explaining how and why this all works.

Per fuzz testing by Andreas Seltenreich.

Report: <871t5pgwdt.fsf@credativ.de>
This commit is contained in:
Tom Lane
2016-04-30 12:29:21 -04:00
parent 17d5db352c
commit c45bf5751b
5 changed files with 71 additions and 34 deletions

View File

@ -73,7 +73,7 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void create_parallel_paths(PlannerInfo *root, RelOptInfo *rel);
static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static bool function_rte_parallel_ok(RangeTblEntry *rte);
@ -447,6 +447,16 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
}
}
/*
* If this is a baserel, consider gathering any partial paths we may have
* created for it. (If we tried to gather inheritance children, we could
* end up with a very large number of gather nodes, each trying to grab
* its own pool of workers, so don't do this for otherrels. Instead,
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
generate_gather_paths(root, rel);
/*
* Allow a plugin to editorialize on the set of Paths for this base
* relation. It could add new paths (such as CustomPaths) by calling
@ -643,7 +653,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
create_parallel_paths(root, rel);
create_plain_partial_paths(root, rel);
/* Consider index scans */
create_index_paths(root, rel);
@ -653,11 +663,11 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
}
/*
* create_parallel_paths
* Build parallel access paths for a plain relation
* create_plain_partial_paths
* Build partial access paths for parallel scan of a plain relation
*/
static void
create_parallel_paths(PlannerInfo *root, RelOptInfo *rel)
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
{
int parallel_degree = 1;
@ -712,16 +722,6 @@ create_parallel_paths(PlannerInfo *root, RelOptInfo *rel)
/* Add an unordered partial path based on a parallel sequential scan. */
add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_degree));
/*
* If this is a baserel, consider gathering any partial paths we may have
* just created. If we gathered an inheritance child, we could end up
* with a very large number of gather nodes, each trying to grab its own
* pool of workers, so don't do this in that case. Instead, we'll
* consider gathering partial paths for the appendrel.
*/
if (rel->reloptkind == RELOPT_BASEREL)
generate_gather_paths(root, rel);
}
/*
@ -1262,9 +1262,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
appendpath = create_append_path(rel, partial_subpaths, NULL,
parallel_degree);
add_partial_path(rel, (Path *) appendpath);
/* Consider gathering it. */
generate_gather_paths(root, rel);
}
/*
@ -1970,6 +1967,10 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
* generate_gather_paths
* Generate parallel access paths for a relation by pushing a Gather on
* top of a partial path.
*
* This must not be called until after we're done creating all partial paths
* for the specified relation. (Otherwise, add_partial_path might delete a
* path that some GatherPath has a reference to.)
*/
void
generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
@ -1983,7 +1984,9 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
/*
* The output of Gather is currently always unsorted, so there's only one
* partial path of interest: the cheapest one.
* partial path of interest: the cheapest one. That will be the one at
* the front of partial_pathlist because of the way add_partial_path
* works.
*
* Eventually, we should have a Gather Merge operation that can merge
* multiple tuple streams together while preserving their ordering. We
@ -2148,12 +2151,19 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
join_search_one_level(root, lev);
/*
* Do cleanup work on each just-processed rel.
* Run generate_gather_paths() for each just-processed joinrel. We
* could not do this earlier because both regular and partial paths
* can get added to a particular joinrel at multiple times within
* join_search_one_level. After that, we're done creating paths
* for the joinrel, so run set_cheapest().
*/
foreach(lc, root->join_rel_level[lev])
{
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
generate_gather_paths(root, rel);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);