]> granicus.if.org Git - postgresql/commitdiff
Force READY portals into FAILED state when a transaction or subtransaction
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 18 Feb 2010 03:06:46 +0000 (03:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 18 Feb 2010 03:06:46 +0000 (03:06 +0000)
is aborted, if they were created within the failed xact.  This prevents
ExecutorEnd from being run on them, which is a good idea because they may
contain references to tables or other objects that no longer exist.
In particular this is hazardous when auto_explain is active, but it's
really rather surprising that nobody has seen an issue with this before.
I'm back-patching this to 8.4, since that's the first version that contains
auto_explain or an ExecutorEnd hook, but I wonder whether we shouldn't
back-patch further.

src/backend/utils/mmgr/portalmem.c

index 889d4653e14fa00628001cc48509f28930ea0925..58d9da4301f213e13bed5ff92f8c63f84289e388 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.116 2010/01/18 02:30:25 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.117 2010/02/18 03:06:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -668,6 +668,7 @@ AtAbort_Portals(void)
        {
                Portal          portal = hentry->portal;
 
+               /* Any portal that was actually running has to be considered broken */
                if (portal->status == PORTAL_ACTIVE)
                        portal->status = PORTAL_FAILED;
 
@@ -677,6 +678,15 @@ AtAbort_Portals(void)
                if (portal->createSubid == InvalidSubTransactionId)
                        continue;
 
+               /*
+                * If it was created in the current transaction, we can't do normal
+                * shutdown on a READY portal either; it might refer to objects
+                * created in the failed transaction.  See comments in
+                * AtSubAbort_Portals.
+                */
+               if (portal->status == PORTAL_READY)
+                       portal->status = PORTAL_FAILED;
+
                /* let portalcmds.c clean up the state it knows about */
                if (PointerIsValid(portal->cleanup))
                {
@@ -789,61 +799,41 @@ AtSubAbort_Portals(SubTransactionId mySubid,
                        continue;
 
                /*
-                * Force any active portals of my own transaction into FAILED state.
-                * This is mostly to ensure that a portal running a FETCH will go
-                * FAILED if the underlying cursor fails.  (Note we do NOT want to do
-                * this to upper-level portals, since they may be able to continue.)
-                *
-                * This is only needed to dodge the sanity check in PortalDrop.
+                * 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.)
                 */
-               if (portal->status == PORTAL_ACTIVE)
+               if (portal->status == PORTAL_READY ||
+                       portal->status == PORTAL_ACTIVE)
                        portal->status = PORTAL_FAILED;
 
-               /*
-                * If the portal is READY then allow it to survive into the parent
-                * transaction; otherwise shut it down.
-                *
-                * Currently, we can't actually support that because the portal's
-                * query might refer to objects created or changed in the failed
-                * subtransaction, leading to crashes if execution is resumed. So,
-                * even READY portals are deleted.      It would be nice to detect whether
-                * the query actually depends on any such object, instead.
-                */
-#ifdef NOT_USED
-               if (portal->status == PORTAL_READY)
+               /* let portalcmds.c clean up the state it knows about */
+               if (PointerIsValid(portal->cleanup))
                {
-                       portal->createSubid = parentSubid;
-                       if (portal->resowner)
-                               ResourceOwnerNewParent(portal->resowner, parentXactOwner);
+                       (*portal->cleanup) (portal);
+                       portal->cleanup = NULL;
                }
-               else
-#endif
-               {
-                       /* let portalcmds.c clean up the state it knows about */
-                       if (PointerIsValid(portal->cleanup))
-                       {
-                               (*portal->cleanup) (portal);
-                               portal->cleanup = NULL;
-                       }
 
-                       /* drop cached plan reference, if any */
-                       PortalReleaseCachedPlan(portal);
+               /* drop cached plan reference, if any */
+               PortalReleaseCachedPlan(portal);
 
-                       /*
-                        * Any resources belonging to the portal will be released in the
-                        * upcoming transaction-wide cleanup; they will be gone before we
-                        * run PortalDrop.
-                        */
-                       portal->resowner = NULL;
+               /*
+                * Any resources belonging to the portal will be released in the
+                * upcoming transaction-wide cleanup; they will be gone before we
+                * run PortalDrop.
+                */
+               portal->resowner = NULL;
 
-                       /*
-                        * Although we can't delete the portal data structure proper, we
-                        * can release any memory in subsidiary contexts, such as executor
-                        * state.  The cleanup hook was the last thing that might have
-                        * needed data there.
-                        */
-                       MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
-               }
+               /*
+                * Although we can't delete the portal data structure proper, we
+                * can release any memory in subsidiary contexts, such as executor
+                * state.  The cleanup hook was the last thing that might have
+                * needed data there.
+                */
+               MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
        }
 }