diff options
author | Tom Lane | 2015-09-04 17:36:49 +0000 |
---|---|---|
committer | Tom Lane | 2015-09-04 17:37:14 +0000 |
commit | c5454f99c49fce01ce946b5f52a4929c21d5f229 (patch) | |
tree | f2f1fa158305670185053fa8157d063f57246e8b /src/backend/utils/mmgr/portalmem.c | |
parent | 1bbd52cb9a4aa61a7dd751f5d1f7b44650d6122a (diff) |
Fix subtransaction cleanup after an outer-subtransaction portal fails.
Formerly, we treated only portals created in the current subtransaction as
having failed during subtransaction abort. However, if the error occurred
while running a portal created in an outer subtransaction (ie, a cursor
declared before the last savepoint), that has to be considered broken too.
To allow reliable detection of which ones those are, add a bookkeeping
field to struct Portal that tracks the innermost subtransaction in which
each portal has actually been executed. (Without this, we'd end up
failing portals containing functions that had called the subtransaction,
thereby breaking plpgsql exception blocks completely.)
In addition, when we fail an outer-subtransaction Portal, transfer its
resources into the subtransaction's resource owner, so that they're
released early in cleanup of the subxact. This fixes a problem reported by
Jim Nasby in which a function executed in an outer-subtransaction cursor
could cause an Assert failure or crash by referencing a relation created
within the inner subtransaction.
The proximate cause of the Assert failure is that AtEOSubXact_RelationCache
assumed it could blow away a relcache entry without first checking that the
entry had zero refcount. That was a bad idea on its own terms, so add such
a check there, and to the similar coding in AtEOXact_RelationCache. This
provides an independent safety measure in case there are still ways to
provoke the situation despite the Portal-level changes.
This has been broken since subtransactions were invented, so back-patch
to all supported branches.
Tom Lane and Michael Paquier
Diffstat (limited to 'src/backend/utils/mmgr/portalmem.c')
-rw-r--r-- | src/backend/utils/mmgr/portalmem.c | 82 |
1 files changed, 74 insertions, 8 deletions
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 75304986a07..44a87f7dea8 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -232,6 +232,7 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent) portal->status = PORTAL_NEW; portal->cleanup = PortalCleanup; portal->createSubid = GetCurrentSubTransactionId(); + portal->activeSubid = portal->createSubid; portal->strategy = PORTAL_MULTI_QUERY; portal->cursorOptions = CURSOR_OPT_NO_SCROLL; portal->atStart = true; @@ -403,6 +404,25 @@ UnpinPortal(Portal portal) } /* + * MarkPortalActive + * Transition a portal from READY to ACTIVE state. + * + * NOTE: never set portal->status = PORTAL_ACTIVE directly; call this instead. + */ +void +MarkPortalActive(Portal portal) +{ + /* For safety, this is a runtime test not just an Assert */ + if (portal->status != PORTAL_READY) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("portal \"%s\" cannot be run", portal->name))); + /* Perform the state transition */ + portal->status = PORTAL_ACTIVE; + portal->activeSubid = GetCurrentSubTransactionId(); +} + +/* * MarkPortalDone * Transition a portal from ACTIVE to DONE state. * @@ -690,6 +710,7 @@ PreCommit_Portals(bool isPrepare) * not belonging to this transaction. */ portal->createSubid = InvalidSubTransactionId; + portal->activeSubid = InvalidSubTransactionId; /* Report we changed state */ result = true; @@ -836,8 +857,8 @@ AtCleanup_Portals(void) /* * Pre-subcommit processing for portals. * - * Reassign the portals created in the current subtransaction to the parent - * subtransaction. + * Reassign portals created or used in the current subtransaction to the + * parent subtransaction. */ void AtSubCommit_Portals(SubTransactionId mySubid, @@ -859,14 +880,16 @@ AtSubCommit_Portals(SubTransactionId mySubid, if (portal->resowner) ResourceOwnerNewParent(portal->resowner, parentXactOwner); } + if (portal->activeSubid == mySubid) + portal->activeSubid = parentSubid; } } /* * Subtransaction abort handling for portals. * - * Deactivate portals created during the failed subtransaction. - * Note that per AtSubCommit_Portals, this will catch portals created + * Deactivate portals created or used during the failed subtransaction. + * Note that per AtSubCommit_Portals, this will catch portals created/used * in descendants of the subtransaction too. * * We don't destroy any portals here; that's done in AtSubCleanup_Portals. @@ -874,6 +897,7 @@ AtSubCommit_Portals(SubTransactionId mySubid, void AtSubAbort_Portals(SubTransactionId mySubid, SubTransactionId parentSubid, + ResourceOwner myXactOwner, ResourceOwner parentXactOwner) { HASH_SEQ_STATUS status; @@ -885,16 +909,58 @@ AtSubAbort_Portals(SubTransactionId mySubid, { Portal portal = hentry->portal; + /* Was it created in this subtransaction? */ if (portal->createSubid != mySubid) + { + /* No, but maybe it was used in this subtransaction? */ + if (portal->activeSubid == mySubid) + { + /* Maintain activeSubid until the portal is removed */ + portal->activeSubid = parentSubid; + + /* + * Upper-level portals that failed while running in this + * subtransaction must be forced into FAILED state, for the + * same reasons discussed below. + * + * We assume we can get away without forcing upper-level READY + * portals to fail, even if they were run and then suspended. + * In theory a suspended upper-level portal could have + * acquired some references to objects that are about to be + * destroyed, but there should be sufficient defenses against + * such cases: the portal's original query cannot contain such + * references, and any references within, say, cached plans of + * PL/pgSQL functions are not from active queries and should + * be protected by revalidation logic. + */ + if (portal->status == PORTAL_ACTIVE) + MarkPortalFailed(portal); + + /* + * Also, if we failed it during the current subtransaction + * (either just above, or earlier), reattach its resource + * owner to the current subtransaction's resource owner, so + * that any resources it still holds will be released while + * cleaning up this subtransaction. This prevents some corner + * cases wherein we might get Asserts or worse while cleaning + * up objects created during the current subtransaction + * (because they're still referenced within this portal). + */ + if (portal->status == PORTAL_FAILED && portal->resowner) + { + ResourceOwnerNewParent(portal->resowner, myXactOwner); + portal->resowner = NULL; + } + } + /* Done if it wasn't created in this subtransaction */ continue; + } /* * Force any live portals of my own subtransaction into FAILED state. * We have to do this because they might refer to objects created or - * changed in the failed subtransaction, leading to crashes if - * execution is resumed, or even if we just try to run ExecutorEnd. - * (Note we do NOT do this to upper-level portals, since they cannot - * have such references and hence may be able to continue.) + * changed in the failed subtransaction, leading to crashes within + * ExecutorEnd when portalcmds.c tries to close down the portal. */ if (portal->status == PORTAL_READY || portal->status == PORTAL_ACTIVE) |