diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/namespace.c | 98 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 154 | ||||
-rw-r--r-- | src/backend/utils/resowner/resowner.c | 24 |
3 files changed, 266 insertions, 10 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 5ff78248e0d..2ec23016fe5 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -126,6 +126,11 @@ * namespaceUser is the userid the path has been computed for. * * Note: all data pointed to by these List variables is in TopMemoryContext. + * + * activePathGeneration is incremented whenever the effective values of + * activeSearchPath/activeCreationNamespace/activeTempCreationPending change. + * This can be used to quickly detect whether any change has happened since + * a previous examination of the search path state. */ /* These variables define the actually active state: */ @@ -138,6 +143,9 @@ static Oid activeCreationNamespace = InvalidOid; /* if true, activeCreationNamespace is wrong, it should be temp namespace */ static bool activeTempCreationPending = false; +/* current generation counter; make sure this is never zero */ +static uint64 activePathGeneration = 1; + /* These variables are the values last derived from namespace_search_path: */ static List *baseSearchPath = NIL; @@ -3373,6 +3381,7 @@ GetOverrideSearchPath(MemoryContext context) schemas = list_delete_first(schemas); } result->schemas = schemas; + result->generation = activePathGeneration; MemoryContextSwitchTo(oldcxt); @@ -3393,12 +3402,18 @@ CopyOverrideSearchPath(OverrideSearchPath *path) result->schemas = list_copy(path->schemas); result->addCatalog = path->addCatalog; result->addTemp = path->addTemp; + result->generation = path->generation; return result; } /* * OverrideSearchPathMatchesCurrent - does path match current setting? + * + * This is tested over and over in some common code paths, and in the typical + * scenario where the active search path seldom changes, it'll always succeed. + * We make that case fast by keeping a generation counter that is advanced + * whenever the active search path changes. */ bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) @@ -3408,6 +3423,10 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) recomputeNamespacePath(); + /* Quick out if already known equal to active path. */ + if (path->generation == activePathGeneration) + return true; + /* We scan down the activeSearchPath to see if it matches the input. */ lc = list_head(activeSearchPath); @@ -3440,6 +3459,13 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) } if (lc) return false; + + /* + * Update path->generation so that future tests will return quickly, so + * long as the active search path doesn't change. + */ + path->generation = activePathGeneration; + return true; } @@ -3510,6 +3536,14 @@ PushOverrideSearchPath(OverrideSearchPath *newpath) activeCreationNamespace = entry->creationNamespace; activeTempCreationPending = false; /* XXX is this OK? */ + /* + * We always increment activePathGeneration when pushing/popping an + * override path. In current usage, these actions always change the + * effective path state, so there's no value in checking to see if it + * didn't change. + */ + activePathGeneration++; + MemoryContextSwitchTo(oldcxt); } @@ -3551,6 +3585,9 @@ PopOverrideSearchPath(void) activeCreationNamespace = baseCreationNamespace; activeTempCreationPending = baseTempCreationPending; } + + /* As above, the generation always increments. */ + activePathGeneration++; } @@ -3707,6 +3744,7 @@ recomputeNamespacePath(void) ListCell *l; bool temp_missing; Oid firstNS; + bool pathChanged; MemoryContext oldcxt; /* Do nothing if an override search spec is active. */ @@ -3814,18 +3852,31 @@ recomputeNamespacePath(void) oidlist = lcons_oid(myTempNamespace, oidlist); /* - * Now that we've successfully built the new list of namespace OIDs, save - * it in permanent storage. + * We want to detect the case where the effective value of the base search + * path variables didn't change. As long as we're doing so, we can avoid + * copying the OID list unncessarily. */ - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - newpath = list_copy(oidlist); - MemoryContextSwitchTo(oldcxt); + if (baseCreationNamespace == firstNS && + baseTempCreationPending == temp_missing && + equal(oidlist, baseSearchPath)) + { + pathChanged = false; + } + else + { + pathChanged = true; + + /* Must save OID list in permanent storage. */ + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + newpath = list_copy(oidlist); + MemoryContextSwitchTo(oldcxt); - /* Now safe to assign to state variables. */ - list_free(baseSearchPath); - baseSearchPath = newpath; - baseCreationNamespace = firstNS; - baseTempCreationPending = temp_missing; + /* Now safe to assign to state variables. */ + list_free(baseSearchPath); + baseSearchPath = newpath; + baseCreationNamespace = firstNS; + baseTempCreationPending = temp_missing; + } /* Mark the path valid. */ baseSearchPathValid = true; @@ -3836,6 +3887,16 @@ recomputeNamespacePath(void) activeCreationNamespace = baseCreationNamespace; activeTempCreationPending = baseTempCreationPending; + /* + * Bump the generation only if something actually changed. (Notice that + * what we compared to was the old state of the base path variables; so + * this does not deal with the situation where we have just popped an + * override path and restored the prior state of the base path. Instead + * we rely on the override-popping logic to have bumped the generation.) + */ + if (pathChanged) + activePathGeneration++; + /* Clean up. */ pfree(rawname); list_free(namelist); @@ -4054,6 +4115,8 @@ AtEOXact_Namespace(bool isCommit, bool parallel) activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; activeTempCreationPending = baseTempCreationPending; + /* Always bump generation --- see note in recomputeNamespacePath */ + activePathGeneration++; } } @@ -4109,6 +4172,8 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, overrideStack = list_delete_first(overrideStack); list_free(entry->searchPath); pfree(entry); + /* Always bump generation --- see note in recomputeNamespacePath */ + activePathGeneration++; } /* Activate the next level down. */ @@ -4118,6 +4183,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, activeSearchPath = entry->searchPath; activeCreationNamespace = entry->creationNamespace; activeTempCreationPending = false; /* XXX is this OK? */ + + /* + * It's probably unnecessary to bump generation here, but this should + * not be a performance-critical case, so better to be over-cautious. + */ + activePathGeneration++; } else { @@ -4125,6 +4196,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; activeTempCreationPending = baseTempCreationPending; + + /* + * If we popped an override stack entry, then we already bumped the + * generation above. If we did not, then the above assignments did + * nothing and we need not bump the generation. + */ } } @@ -4264,6 +4341,7 @@ InitializeSearchPath(void) activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; activeTempCreationPending = baseTempCreationPending; + activePathGeneration++; /* pro forma */ } else { diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index dbae18d68c6..8e27b03cbb6 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -1278,6 +1278,160 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) } /* + * CachedPlanAllowsSimpleValidityCheck: can we use CachedPlanIsSimplyValid? + * + * This function, together with CachedPlanIsSimplyValid, provides a fast path + * for revalidating "simple" generic plans. The core requirement to be simple + * is that the plan must not require taking any locks, which translates to + * not touching any tables; this happens to match up well with an important + * use-case in PL/pgSQL. This function tests whether that's true, along + * with checking some other corner cases that we'd rather not bother with + * handling in the fast path. (Note that it's still possible for such a plan + * to be invalidated, for example due to a change in a function that was + * inlined into the plan.) + * + * This must only be called on known-valid generic plans (eg, ones just + * returned by GetCachedPlan). If it returns true, the caller may re-use + * the cached plan as long as CachedPlanIsSimplyValid returns true; that + * check is much cheaper than the full revalidation done by GetCachedPlan. + * Nonetheless, no required checks are omitted. + */ +bool +CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, + CachedPlan *plan) +{ + ListCell *lc; + + /* Sanity-check that the caller gave us a validated generic plan. */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plan->magic == CACHEDPLAN_MAGIC); + Assert(plansource->is_valid); + Assert(plan->is_valid); + Assert(plan == plansource->gplan); + + /* We don't support oneshot plans here. */ + if (plansource->is_oneshot) + return false; + Assert(!plan->is_oneshot); + + /* + * If the plan is dependent on RLS considerations, or it's transient, + * reject. These things probably can't ever happen for table-free + * queries, but for safety's sake let's check. + */ + if (plansource->dependsOnRLS) + return false; + if (plan->dependsOnRole) + return false; + if (TransactionIdIsValid(plan->saved_xmin)) + return false; + + /* + * Reject if AcquirePlannerLocks would have anything to do. This is + * simplistic, but there's no need to inquire any more carefully; indeed, + * for current callers it shouldn't even be possible to hit any of these + * checks. + */ + foreach(lc, plansource->query_list) + { + Query *query = lfirst_node(Query, lc); + + if (query->commandType == CMD_UTILITY) + return false; + if (query->rtable || query->cteList || query->hasSubLinks) + return false; + } + + /* + * Reject if AcquireExecutorLocks would have anything to do. This is + * probably unnecessary given the previous check, but let's be safe. + */ + foreach(lc, plan->stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + ListCell *lc2; + + if (plannedstmt->commandType == CMD_UTILITY) + return false; + + /* + * We have to grovel through the rtable because it's likely to contain + * an RTE_RESULT relation, rather than being totally empty. + */ + foreach(lc2, plannedstmt->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + + if (rte->rtekind == RTE_RELATION) + return false; + } + } + + /* + * Okay, it's simple. Note that what we've primarily established here is + * that no locks need be taken before checking the plan's is_valid flag. + */ + return true; +} + +/* + * CachedPlanIsSimplyValid: quick check for plan still being valid + * + * This function must not be used unless CachedPlanAllowsSimpleValidityCheck + * previously said it was OK. + * + * If the plan is valid, and "owner" is not NULL, record a refcount on + * the plan in that resowner before returning. It is caller's responsibility + * to be sure that a refcount is held on any plan that's being actively used. + * + * The code here is unconditionally safe as long as the only use of this + * CachedPlanSource is in connection with the particular CachedPlan pointer + * that's passed in. If the plansource were being used for other purposes, + * it's possible that its generic plan could be invalidated and regenerated + * while the current caller wasn't looking, and then there could be a chance + * collision of address between this caller's now-stale plan pointer and the + * actual address of the new generic plan. For current uses, that scenario + * can't happen; but with a plansource shared across multiple uses, it'd be + * advisable to also save plan->generation and verify that that still matches. + */ +bool +CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan, + ResourceOwner owner) +{ + /* + * Careful here: since the caller doesn't necessarily hold a refcount on + * the plan to start with, it's possible that "plan" is a dangling + * pointer. Don't dereference it until we've verified that it still + * matches the plansource's gplan (which is either valid or NULL). + */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* + * Has cache invalidation fired on this plan? We can check this right + * away since there are no locks that we'd need to acquire first. + */ + if (!plansource->is_valid || plan != plansource->gplan || !plan->is_valid) + return false; + + Assert(plan->magic == CACHEDPLAN_MAGIC); + + /* Is the search_path still the same as when we made it? */ + Assert(plansource->search_path != NULL); + if (!OverrideSearchPathMatchesCurrent(plansource->search_path)) + return false; + + /* It's still good. Bump refcount if requested. */ + if (owner) + { + ResourceOwnerEnlargePlanCacheRefs(owner); + plan->refcount++; + ResourceOwnerRememberPlanCacheRef(owner, plan); + } + + return true; +} + +/* * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context * * This can only be applied to unsaved plans; once saved, a plan always diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 3c39e48825a..8bc2c4e9ea3 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -679,6 +679,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, } /* + * ResourceOwnerReleaseAllPlanCacheRefs + * Release the plancache references (only) held by this owner. + * + * We might eventually add similar functions for other resource types, + * but for now, only this is needed. + */ +void +ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner) +{ + ResourceOwner save; + Datum foundres; + + save = CurrentResourceOwner; + CurrentResourceOwner = owner; + while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + { + CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + + ReleaseCachedPlan(res, true); + } + CurrentResourceOwner = save; +} + +/* * ResourceOwnerDelete * Delete an owner object and its descendants. * |