On 22/06/10 00:47, Heikki Linnakangas wrote:
Maybe it would be easier to somehow protect the portal then, and throw an error if you try to close it. We could just mark the portal as PORTAL_ACTIVE while we run the user statements, but that would also forbid fetching or moving it. I'm thinking of a new "pinned" state, which is like PORTAL_READY except that the portal can't be dropped like in PORTAL_ACTIVE state.
Like this. (I'll need to revert the broken commit before applying this) -- Heikki Linnakangas EnterpriseDB http://www.enterprisedb.com
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 8ad4915..9f15772 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -723,6 +723,7 @@ PortalRun(Portal portal, long count, bool isTopLevel, ResourceOwner saveResourceOwner; MemoryContext savePortalContext; MemoryContext saveMemoryContext; + PortalStatus readyStatus; AssertArg(PortalIsValid(portal)); @@ -742,10 +743,12 @@ PortalRun(Portal portal, long count, bool isTopLevel, /* * Check for improper portal use, and mark portal active. */ - if (portal->status != PORTAL_READY) + if (portal->status != PORTAL_READY && + portal->status != PORTAL_READY_NOCLOSE) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("portal \"%s\" cannot be run", portal->name))); + readyStatus = portal->status; portal->status = PORTAL_ACTIVE; /* @@ -810,7 +813,7 @@ PortalRun(Portal portal, long count, bool isTopLevel, } /* Mark portal not active */ - portal->status = PORTAL_READY; + portal->status = readyStatus; /* * Since it's a forward fetch, say DONE iff atEnd is now true. @@ -1359,16 +1362,19 @@ PortalRunFetch(Portal portal, ResourceOwner saveResourceOwner; MemoryContext savePortalContext; MemoryContext oldContext; + PortalStatus readyStatus; AssertArg(PortalIsValid(portal)); /* * Check for improper portal use, and mark portal active. */ - if (portal->status != PORTAL_READY) + if (portal->status != PORTAL_READY && + portal->status != PORTAL_READY_NOCLOSE) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("portal \"%s\" cannot be run", portal->name))); + readyStatus = portal->status; portal->status = PORTAL_ACTIVE; /* @@ -1430,7 +1436,7 @@ PortalRunFetch(Portal portal, MemoryContextSwitchTo(oldContext); /* Mark portal not active */ - portal->status = PORTAL_READY; + portal->status = readyStatus; ActivePortal = saveActivePortal; CurrentResourceOwner = saveResourceOwner; diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index ac62d45..28e854d 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -377,6 +377,27 @@ PortalCreateHoldStore(Portal portal) } /* + * PinPortal + * Protect a portal from dropping + */ +void +PinPortal(Portal portal) +{ + if (portal->status != PORTAL_READY) + elog(ERROR, "cannot pin portal with status %u", portal->status); + + portal->status = PORTAL_READY_NOCLOSE; +} + +void +UnpinPortal(Portal portal) +{ + if (portal->status != PORTAL_READY_NOCLOSE) + elog(ERROR, "portal not pinned"); + portal->status = PORTAL_READY; +} + +/* * PortalDrop * Destroy the portal. */ @@ -386,8 +407,11 @@ PortalDrop(Portal portal, bool isTopCommit) AssertArg(PortalIsValid(portal)); /* Not sure if this case can validly happen or not... */ - if (portal->status == PORTAL_ACTIVE) - elog(ERROR, "cannot drop active portal"); + if (portal->status == PORTAL_ACTIVE || + portal->status == PORTAL_READY_NOCLOSE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cannot drop active portal \"%s\"", portal->name))); /* * Remove portal from hash table. Because we do this first, we will not @@ -490,7 +514,8 @@ PortalHashTableDeleteAll(void) { Portal portal = hentry->portal; - if (portal->status != PORTAL_ACTIVE) + if (portal->status != PORTAL_ACTIVE && + portal->status != PORTAL_READY_NOCLOSE) PortalDrop(portal, false); } } @@ -631,6 +656,13 @@ AtCommit_Portals(void) } /* + * There should be no pinned portals anymore. Complain if someone + * leaked one. + */ + if (portal->status == PORTAL_READY_NOCLOSE) + elog(ERROR, "cannot commit while a portal is pinned"); + + /* * Do nothing to cursors held over from a previous transaction * (including holdable ones just frozen by CommitHoldablePortals). */ @@ -684,7 +716,8 @@ AtAbort_Portals(void) * created in the failed transaction. See comments in * AtSubAbort_Portals. */ - if (portal->status == PORTAL_READY) + if (portal->status == PORTAL_READY || + portal->status == PORTAL_READY_NOCLOSE) portal->status = PORTAL_FAILED; /* let portalcmds.c clean up the state it knows about */ @@ -807,6 +840,7 @@ AtSubAbort_Portals(SubTransactionId mySubid, * have such references and hence may be able to continue.) */ if (portal->status == PORTAL_READY || + portal->status == PORTAL_READY_NOCLOSE || portal->status == PORTAL_ACTIVE) portal->status = PORTAL_FAILED; diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index ce2f38a..b5dc3af 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -88,15 +88,17 @@ typedef enum PortalStrategy } PortalStrategy; /* - * A portal is always in one of these states. It is possible to transit - * from ACTIVE back to READY if the query is not run to completion; - * otherwise we never back up in status. + * A portal is always in one of these states. It is possible to transit + * from ACTIVE back to previous READY/READY_NOCLOSE state if the query is + * not run to completion. Also, pinning/unpinning a portal moves it + * between READY and READY_NOCLOSE. Otherwise we never back up in status. */ typedef enum PortalStatus { PORTAL_NEW, /* freshly created */ PORTAL_DEFINED, /* PortalDefineQuery done */ PORTAL_READY, /* PortalStart complete, can run it */ + PORTAL_READY_NOCLOSE, /* pinned -- like READY, but can't delete it */ PORTAL_ACTIVE, /* portal is running (can't delete it) */ PORTAL_DONE, /* portal is finished (don't re-run it) */ PORTAL_FAILED /* portal got error (can't re-run it) */ @@ -199,6 +201,8 @@ extern void AtSubAbort_Portals(SubTransactionId mySubid, extern void AtSubCleanup_Portals(SubTransactionId mySubid); extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent); extern Portal CreateNewPortal(void); +extern void PinPortal(Portal portal); +extern void UnpinPortal(Portal portal); extern void PortalDrop(Portal portal, bool isTopCommit); extern Portal GetPortalByName(const char *name); extern void PortalDefineQuery(Portal portal, diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 0f52eae..2ceccc5 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -4290,6 +4290,12 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, elog(ERROR, "unsupported target"); /* + * Make sure the portal doesn't get closed by the user statements + * we execute. + */ + PinPortal(portal); + + /* * Fetch the initial tuple(s). If prefetching is allowed then we grab a * few more rows to avoid multiple trips through executor startup * overhead. @@ -4399,6 +4405,8 @@ loop_exit: */ SPI_freetuptable(tuptab); + UnpinPortal(portal); + /* * Set the FOUND variable to indicate the result of executing the loop * (namely, whether we looped one or more times). This must be set last so
-- Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-bugs