diff options
author | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2010-07-05 09:27:24 +0000 |
---|---|---|
committer | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2010-07-05 09:27:24 +0000 |
commit | 0f0b236b0311fc78f37cfb526685a8a105643f6d (patch) | |
tree | c3fb7c710ce41b63363e4b3644141f31964d8e57 /src/backend | |
parent | bf53e7938d14d9cbe22d5756c76529a882420085 (diff) | |
download | postgresql-0f0b236b0311fc78f37cfb526685a8a105643f6d.tar.gz postgresql-0f0b236b0311fc78f37cfb526685a8a105643f6d.zip |
The previous fix in CVS HEAD and 8.4 for handling the case where a cursor
being used in a PL/pgSQL FOR loop is closed was inadequate, as Tom Lane
pointed out. The bug affects FOR statement variants too, because you can
close an implicitly created cursor too by guessing the "<unnamed portal X>"
name created for it.
To fix that, "pin" the portal to prevent it from being dropped while it's
being used in a PL/pgSQL FOR loop. Backpatch all the way to 7.4 which is
the oldest supported version.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/utils/mmgr/portalmem.c | 54 |
1 files changed, 49 insertions, 5 deletions
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 7b53feb4432..ff84b589105 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -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.113.2.3 2010/02/18 03:06:53 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.113.2.4 2010/07/05 09:27:24 heikki Exp $ * *------------------------------------------------------------------------- */ @@ -377,6 +377,28 @@ PortalCreateHoldStore(Portal portal) } /* + * PinPortal + * Protect a portal from dropping. + */ +void +PinPortal(Portal portal) +{ + if (portal->portalPinned) + elog(ERROR, "portal already pinned"); + + portal->portalPinned = true; +} + +void +UnpinPortal(Portal portal) +{ + if (!portal->portalPinned) + elog(ERROR, "portal not pinned"); + + portal->portalPinned = false; +} + +/* * PortalDrop * Destroy the portal. */ @@ -385,9 +407,16 @@ 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"); + /* + * Don't allow dropping a pinned portal, it's still needed by whoever + * pinned it. Not sure if the PORTAL_ACTIVE case can validly happen or + * not... + */ + if (portal->portalPinned || + portal->status == PORTAL_ACTIVE) + 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 @@ -631,6 +660,13 @@ AtCommit_Portals(void) } /* + * There should be no pinned portals anymore. Complain if someone + * leaked one. + */ + if (portal->portalPinned) + 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). */ @@ -738,7 +774,15 @@ AtCleanup_Portals(void) continue; } - /* Else zap it. */ + /* + * If a portal is still pinned, forcibly unpin it. PortalDrop will + * not let us drop the portal otherwise. Whoever pinned the portal + * was interrupted by the abort too and won't try to use it anymore. + */ + if (portal->portalPinned) + portal->portalPinned = false; + + /* Zap it. */ PortalDrop(portal, false); } } |