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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs