]> granicus.if.org Git - postgresql/commitdiff
Fix planner crash from pfree'ing a partial path that a GatherPath uses.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Apr 2016 16:29:21 +0000 (12:29 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Apr 2016 16:29:21 +0000 (12:29 -0400)
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>

src/backend/optimizer/README
src/backend/optimizer/geqo/geqo_eval.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/util/pathnode.c

index 24071729282932ebd86d4819f831d611c9857768..799e0ebe810879b05c2c4ea462ead4ed57f475ad 100644 (file)
@@ -167,6 +167,16 @@ all the ways to produce the same set of joined base rels will share the
 same RelOptInfo, so the paths produced from different join combinations
 that produce equivalent joinrels will compete in add_path().
 
+The dynamic-programming approach has an important property that's not
+immediately obvious: we will finish constructing all paths for a given
+relation before we construct any paths for relations containing that rel.
+This means that we can reliably identify the "cheapest path" for each rel
+before higher-level relations need to know that.  Also, we can safely
+discard a path when we find that another path for the same rel is better,
+without worrying that maybe there is already a reference to that path in
+some higher-level join path.  Without this, memory management for paths
+would be much more complicated.
+
 Once we have built the final join rel, we use either the cheapest path
 for it or the cheapest path with the desired ordering (if that's cheaper
 than applying a sort to the cheapest other path).
@@ -321,8 +331,9 @@ set up for recursive handling of subqueries
         For each joinrel of the prior level, do make_rels_by_clause_joins()
         if it has join clauses, or make_rels_by_clauseless_joins() if not.
         Also generate "bushy plan" joins between joinrels of lower levels.
-      Back at standard_join_search(), apply set_cheapest() to extract the
-      cheapest path for each newly constructed joinrel.
+      Back at standard_join_search(), generate gather paths if needed for
+      each newly constructed joinrel, then apply set_cheapest() to extract
+      the cheapest path for it.
       Loop back if this wasn't the top join level.
   Back at grouping_planner:
   do grouping (GROUP BY) and aggregation
index 26969fc4c92b0bc9e5486981afa5f83b658f6775..88acebc1f2d28b403f6c13deca913467d56b4202 100644 (file)
@@ -266,6 +266,9 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, bool force)
                        /* Keep searching if join order is not valid */
                        if (joinrel)
                        {
+                               /* Create GatherPaths for any useful partial paths for rel */
+                               generate_gather_paths(root, joinrel);
+
                                /* Find and save the cheapest paths for this joinrel */
                                set_cheapest(joinrel);
 
index 17c1edd970360fc35a0b79bb827651cda6a611ba..5246260e12a5f3e5bfef510e3892671f72f80edc 100644 (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);
 
index f8e02b9c45de6a5963fe724f753efcb80b48e9a2..9d06fb2637e8e15cd180361a83fc250136b44718 100644 (file)
@@ -223,12 +223,7 @@ add_paths_to_joinrel(PlannerInfo *root,
                                                                                                 jointype, &extra);
 
        /*
-        * 6. Consider gathering partial paths.
-        */
-       generate_gather_paths(root, joinrel);
-
-       /*
-        * 7. Finally, give extensions a chance to manipulate the path list.
+        * 6. Finally, give extensions a chance to manipulate the path list.
         */
        if (set_join_pathlist_hook)
                set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
index 89cae793ca3b26224bbd1c02b73a82777581fb4f..6182b362a3a92f71bf879e0df66cbab350c67446 100644 (file)
@@ -394,8 +394,14 @@ set_cheapest(RelOptInfo *parent_rel)
  *       but just recycling discarded Path nodes is a very useful savings in
  *       a large join tree.  We can recycle the List nodes of pathlist, too.
  *
- *       BUT: we do not pfree IndexPath objects, since they may be referenced as
- *       children of BitmapHeapPaths as well as being paths in their own right.
+ *       As noted in optimizer/README, deleting a previously-accepted Path is
+ *       safe because we know that Paths of this rel cannot yet be referenced
+ *       from any other rel, such as a higher-level join.  However, in some cases
+ *       it is possible that a Path is referenced by another Path for its own
+ *       rel; we must not delete such a Path, even if it is dominated by the new
+ *       Path.  Currently this occurs only for IndexPath objects, which may be
+ *       referenced as children of BitmapHeapPaths as well as being paths in
+ *       their own right.  Hence, we don't pfree IndexPaths when rejecting them.
  *
  * 'parent_rel' is the relation entry to which the path corresponds.
  * 'new_path' is a potential path for parent_rel.
@@ -711,6 +717,10 @@ add_path_precheck(RelOptInfo *parent_rel,
  *       parallel such that each worker will generate a subset of the path's
  *       overall result.
  *
+ *       As in add_path, the partial_pathlist is kept sorted with the cheapest
+ *       total path in front.  This is depended on by multiple places, which
+ *       just take the front entry as the cheapest path without searching.
+ *
  *       We don't generate parameterized partial paths for several reasons.  Most
  *       importantly, they're not safe to execute, because there's nothing to
  *       make sure that a parallel scan within the parameterized portion of the
@@ -721,8 +731,8 @@ add_path_precheck(RelOptInfo *parent_rel,
  *       side of the plan will be small anyway.  There could be rare cases where
  *       this wins big - e.g. if join order constraints put a 1-row relation on
  *       the outer side of the topmost join with a parameterized plan on the inner
- *       side - but we'll have to be content not to handle such cases until somebody
- *       builds an executor infrastructure that can cope with them.
+ *       side - but we'll have to be content not to handle such cases until
+ *       somebody builds an executor infrastructure that can cope with them.
  *
  *       Because we don't consider parameterized paths here, we also don't
  *       need to consider the row counts as a measure of quality: every path will
@@ -730,6 +740,14 @@ add_path_precheck(RelOptInfo *parent_rel,
  *       costs: parallelism is only used for plans that will be run to completion.
  *       Therefore, this routine is much simpler than add_path: it needs to
  *       consider only pathkeys and total cost.
+ *
+ *       As with add_path, we pfree paths that are found to be dominated by
+ *       another partial path; this requires that there be no other references to
+ *       such paths yet.  Hence, GatherPaths must not be created for a rel until
+ *       we're done creating all partial paths for it.  We do not currently build
+ *       partial indexscan paths, so there is no need for an exception for
+ *       IndexPaths here; for safety, we instead Assert that a path to be freed
+ *       isn't an IndexPath.
  */
 void
 add_partial_path(RelOptInfo *parent_rel, Path *new_path)
@@ -808,7 +826,7 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path)
                {
                        parent_rel->partial_pathlist =
                                list_delete_cell(parent_rel->partial_pathlist, p1, p1_prev);
-                       /* add_path has a special case for IndexPath; we don't need it */
+                       /* we should not see IndexPaths here, so always safe to delete */
                        Assert(!IsA(old_path, IndexPath));
                        pfree(old_path);
                        /* p1_prev does not advance */
@@ -842,7 +860,7 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path)
        }
        else
        {
-               /* add_path has a special case for IndexPath; we don't need it */
+               /* we should not see IndexPaths here, so always safe to delete */
                Assert(!IsA(new_path, IndexPath));
                /* Reject and recycle the new path */
                pfree(new_path);