1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-05 07:21:24 +03:00

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
This commit is contained in:
Tom Lane
2015-09-04 13:36:49 -04:00
parent 1bbd52cb9a
commit c5454f99c4
8 changed files with 197 additions and 29 deletions

View File

@ -613,6 +613,52 @@ fetch from foo;
(1 row)
abort;
-- Test for proper cleanup after a failure in a cursor portal
-- that was created in an outer subtransaction
CREATE FUNCTION invert(x float8) RETURNS float8 LANGUAGE plpgsql AS
$$ begin return 1/x; end $$;
CREATE FUNCTION create_temp_tab() RETURNS text
LANGUAGE plpgsql AS $$
BEGIN
CREATE TEMP TABLE new_table (f1 float8);
-- case of interest is that we fail while holding an open
-- relcache reference to new_table
INSERT INTO new_table SELECT invert(0.0);
RETURN 'foo';
END $$;
BEGIN;
DECLARE ok CURSOR FOR SELECT * FROM int8_tbl;
DECLARE ctt CURSOR FOR SELECT create_temp_tab();
FETCH ok;
q1 | q2
-----+-----
123 | 456
(1 row)
SAVEPOINT s1;
FETCH ok; -- should work
q1 | q2
-----+------------------
123 | 4567890123456789
(1 row)
FETCH ctt; -- error occurs here
ERROR: division by zero
CONTEXT: PL/pgSQL function invert(double precision) line 1 at RETURN
SQL statement "INSERT INTO new_table SELECT invert(0.0)"
PL/pgSQL function create_temp_tab() line 6 at SQL statement
ROLLBACK TO s1;
FETCH ok; -- should work
q1 | q2
------------------+-----
4567890123456789 | 123
(1 row)
FETCH ctt; -- must be rejected
ERROR: portal "ctt" cannot be run
COMMIT;
DROP FUNCTION create_temp_tab();
DROP FUNCTION invert(x float8);
-- Test for successful cleanup of an aborted transaction at session exit.
-- THIS MUST BE THE LAST TEST IN THIS FILE.
begin;

View File

@ -387,6 +387,38 @@ fetch from foo;
abort;
-- Test for proper cleanup after a failure in a cursor portal
-- that was created in an outer subtransaction
CREATE FUNCTION invert(x float8) RETURNS float8 LANGUAGE plpgsql AS
$$ begin return 1/x; end $$;
CREATE FUNCTION create_temp_tab() RETURNS text
LANGUAGE plpgsql AS $$
BEGIN
CREATE TEMP TABLE new_table (f1 float8);
-- case of interest is that we fail while holding an open
-- relcache reference to new_table
INSERT INTO new_table SELECT invert(0.0);
RETURN 'foo';
END $$;
BEGIN;
DECLARE ok CURSOR FOR SELECT * FROM int8_tbl;
DECLARE ctt CURSOR FOR SELECT create_temp_tab();
FETCH ok;
SAVEPOINT s1;
FETCH ok; -- should work
FETCH ctt; -- error occurs here
ROLLBACK TO s1;
FETCH ok; -- should work
FETCH ctt; -- must be rejected
COMMIT;
DROP FUNCTION create_temp_tab();
DROP FUNCTION invert(x float8);
-- Test for successful cleanup of an aborted transaction at session exit.
-- THIS MUST BE THE LAST TEST IN THIS FILE.