Skip to content

Commit c37ec84

Browse files
committed
Fix longstanding race condition in plancache.c.
When creating or manipulating a cached plan for a transaction control command (particularly ROLLBACK), we must not perform any catalog accesses, since we might be in an aborted transaction. However, plancache.c busily saved or examined the search_path for every cached plan. If we were unlucky enough to do this at a moment where the path's expansion into schema OIDs wasn't already cached, we'd do some catalog accesses; and with some more bad luck such as an ill-timed signal arrival, that could lead to crashes or Assert failures, as exhibited in bug #8095 from Nachiket Vaidya. Fortunately, there's no real need to consider the search path for such commands, so we can just skip the relevant steps when the subject statement is a TransactionStmt. This is somewhat related to bug #5269, though the failure happens during initial cached-plan creation rather than revalidation. This bug has been there since the plan cache was invented, so back-patch to all supported branches.
1 parent e3a7675 commit c37ec84

File tree

1 file changed

+42
-9
lines changed

1 file changed

+42
-9
lines changed

src/backend/utils/cache/plancache.c

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@
6565
#include "utils/syscache.h"
6666

6767

68+
/*
69+
* We must skip "overhead" operations that involve database access when the
70+
* cached plan's subject statement is a transaction control command.
71+
*/
72+
#define IsTransactionStmtPlan(plansource) \
73+
((plansource)->raw_parse_tree && \
74+
IsA((plansource)->raw_parse_tree, TransactionStmt))
75+
6876
/*
6977
* This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
7078
* those that are in long-lived storage and are examined for sinval events).
@@ -352,9 +360,10 @@ CompleteCachedPlan(CachedPlanSource *plansource,
352360
/*
353361
* Use the planner machinery to extract dependencies. Data is saved in
354362
* query_context. (We assume that not a lot of extra cruft is created by
355-
* this call.) We can skip this for one-shot plans.
363+
* this call.) We can skip this for one-shot plans, and transaction
364+
* control commands have no such dependencies anyway.
356365
*/
357-
if (!plansource->is_oneshot)
366+
if (!plansource->is_oneshot && !IsTransactionStmtPlan(plansource))
358367
extract_query_dependencies((Node *) querytree_list,
359368
&plansource->relationOids,
360369
&plansource->invalItems);
@@ -384,9 +393,12 @@ CompleteCachedPlan(CachedPlanSource *plansource,
384393

385394
/*
386395
* Fetch current search_path into dedicated context, but do any
387-
* recalculation work required in caller's context.
396+
* recalculation work required in caller's context. We *must* skip this
397+
* for transaction control commands, because this could result in catalog
398+
* accesses.
388399
*/
389-
plansource->search_path = GetOverrideSearchPath(source_context);
400+
if (!IsTransactionStmtPlan(plansource))
401+
plansource->search_path = GetOverrideSearchPath(source_context);
390402

391403
plansource->is_complete = true;
392404
plansource->is_valid = true;
@@ -537,9 +549,11 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
537549
/*
538550
* For one-shot plans, we do not support revalidation checking; it's
539551
* assumed the query is parsed, planned, and executed in one transaction,
540-
* so that no lock re-acquisition is necessary.
552+
* so that no lock re-acquisition is necessary. Also, there is never
553+
* any need to revalidate plans for transaction control commands (and
554+
* we mustn't risk any catalog accesses when handling those).
541555
*/
542-
if (plansource->is_oneshot)
556+
if (plansource->is_oneshot || IsTransactionStmtPlan(plansource))
543557
{
544558
Assert(plansource->is_valid);
545559
return NIL;
@@ -859,7 +873,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
859873
* compared to parse analysis + planning, I'm not going to contort the
860874
* code enough to avoid that.
861875
*/
862-
PushOverrideSearchPath(plansource->search_path);
876+
if (plansource->search_path)
877+
PushOverrideSearchPath(plansource->search_path);
863878

864879
/*
865880
* If a snapshot is already set (the normal case), we can just use that
@@ -894,7 +909,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
894909
PopActiveSnapshot();
895910

896911
/* Now we can restore current search path */
897-
PopOverrideSearchPath();
912+
if (plansource->search_path)
913+
PopOverrideSearchPath();
898914

899915
/*
900916
* Normally we make a dedicated memory context for the CachedPlan and its
@@ -965,6 +981,9 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
965981
/* Otherwise, never any point in a custom plan if there's no parameters */
966982
if (boundParams == NULL)
967983
return false;
984+
/* ... nor for transaction control statements */
985+
if (IsTransactionStmtPlan(plansource))
986+
return false;
968987

969988
/* See if caller wants to force the decision */
970989
if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)
@@ -1267,7 +1286,8 @@ CopyCachedPlan(CachedPlanSource *plansource)
12671286
newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
12681287
else
12691288
newsource->resultDesc = NULL;
1270-
newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
1289+
if (plansource->search_path)
1290+
newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
12711291
newsource->context = source_context;
12721292

12731293
querytree_context = AllocSetContextCreate(source_context,
@@ -1619,6 +1639,10 @@ PlanCacheRelCallback(Datum arg, Oid relid)
16191639
if (!plansource->is_valid)
16201640
continue;
16211641

1642+
/* Never invalidate transaction control commands */
1643+
if (IsTransactionStmtPlan(plansource))
1644+
continue;
1645+
16221646
/*
16231647
* Check the dependency list for the rewritten querytree.
16241648
*/
@@ -1683,6 +1707,10 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
16831707
if (!plansource->is_valid)
16841708
continue;
16851709

1710+
/* Never invalidate transaction control commands */
1711+
if (IsTransactionStmtPlan(plansource))
1712+
continue;
1713+
16861714
/*
16871715
* Check the dependency list for the rewritten querytree.
16881716
*/
@@ -1772,6 +1800,11 @@ ResetPlanCache(void)
17721800
* We *must not* mark transaction control statements as invalid,
17731801
* particularly not ROLLBACK, because they may need to be executed in
17741802
* aborted transactions when we can't revalidate them (cf bug #5269).
1803+
*/
1804+
if (IsTransactionStmtPlan(plansource))
1805+
continue;
1806+
1807+
/*
17751808
* In general there is no point in invalidating utility statements
17761809
* since they have no plans anyway. So invalidate it only if it
17771810
* contains at least one non-utility statement, or contains a utility

0 commit comments

Comments
 (0)