Skip to content

Commit e6cd857

Browse files
committed
Make RelationFlushRelation() work without ResourceOwner during abort
ReorderBufferImmediateInvalidation() executes invalidation messages in an aborted transaction. However, RelationFlushRelation sometimes required a valid resource owner, to temporarily increment the refcount of the relache entry. Commit b8bff07 worked around that in the main subtransaction abort function, AbortSubTransaction(), but missed this similar case in ReorderBufferImmediateInvalidation(). To fix, introduce a separate function to invalidate a relcache entry. It does the same thing as RelationClearRelation(rebuild==true) does when outside a transaction, but can be called without incrementing the refcount. Add regression test. Before this fix, it failed with: ERROR: ResourceOwnerEnlarge called after release started Reported-by: Alexander Lakhin <[email protected]> Discussion: https://www.postgresql.org/message-id/[email protected]
1 parent 505c008 commit e6cd857

File tree

4 files changed

+122
-20
lines changed

4 files changed

+122
-20
lines changed

contrib/test_decoding/expected/decoding_in_xact.out

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,54 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
7979
COMMIT
8080
(6 rows)
8181

82+
-- Decoding works in transaction that issues DDL
83+
--
84+
-- We had issues handling relcache invalidations with these, see
85+
-- https://www.postgresql.org/message-id/[email protected]
86+
CREATE TABLE tbl_created_outside_xact(id SERIAL PRIMARY KEY);
87+
BEGIN;
88+
-- TRUNCATE changes the relfilenode and sends relcache invalidation
89+
TRUNCATE tbl_created_outside_xact;
90+
INSERT INTO tbl_created_outside_xact(id) VALUES('1');
91+
-- don't show yet, haven't committed
92+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
93+
data
94+
------
95+
(0 rows)
96+
97+
COMMIT;
98+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
99+
data
100+
--------------------------------------------------------------
101+
BEGIN
102+
table public.tbl_created_outside_xact: TRUNCATE: (no-flags)
103+
table public.tbl_created_outside_xact: INSERT: id[integer]:1
104+
COMMIT
105+
(4 rows)
106+
107+
SET debug_logical_replication_streaming = immediate;
108+
BEGIN;
109+
CREATE TABLE tbl_created_in_xact(id SERIAL PRIMARY KEY);
110+
INSERT INTO tbl_created_in_xact VALUES (1);
111+
CHECKPOINT; -- Force WAL flush, so that the above changes will be streamed
112+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
113+
data
114+
------------------------------------------
115+
opening a streamed block for transaction
116+
streaming change for transaction
117+
closing a streamed block for transaction
118+
(3 rows)
119+
120+
COMMIT;
121+
RESET debug_logical_replication_streaming;
122+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
123+
data
124+
---------------------------------------------------------
125+
BEGIN
126+
table public.tbl_created_in_xact: INSERT: id[integer]:1
127+
COMMIT
128+
(3 rows)
129+
82130
SELECT 'stop' FROM pg_drop_replication_slot('regression_slot');
83131
?column?
84132
----------

contrib/test_decoding/sql/decoding_in_xact.sql

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,31 @@ COMMIT;
3838
INSERT INTO nobarf(data) VALUES('3');
3939
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
4040

41+
-- Decoding works in transaction that issues DDL
42+
--
43+
-- We had issues handling relcache invalidations with these, see
44+
-- https://www.postgresql.org/message-id/[email protected]
45+
CREATE TABLE tbl_created_outside_xact(id SERIAL PRIMARY KEY);
46+
BEGIN;
47+
-- TRUNCATE changes the relfilenode and sends relcache invalidation
48+
TRUNCATE tbl_created_outside_xact;
49+
INSERT INTO tbl_created_outside_xact(id) VALUES('1');
50+
51+
-- don't show yet, haven't committed
52+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
53+
COMMIT;
54+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
55+
56+
SET debug_logical_replication_streaming = immediate;
57+
BEGIN;
58+
CREATE TABLE tbl_created_in_xact(id SERIAL PRIMARY KEY);
59+
INSERT INTO tbl_created_in_xact VALUES (1);
60+
61+
CHECKPOINT; -- Force WAL flush, so that the above changes will be streamed
62+
63+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
64+
COMMIT;
65+
RESET debug_logical_replication_streaming;
66+
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
67+
4168
SELECT 'stop' FROM pg_drop_replication_slot('regression_slot');

src/backend/access/transam/xact.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5279,20 +5279,7 @@ AbortSubTransaction(void)
52795279

52805280
AtEOSubXact_RelationCache(false, s->subTransactionId,
52815281
s->parent->subTransactionId);
5282-
5283-
5284-
/*
5285-
* AtEOSubXact_Inval sometimes needs to temporarily bump the refcount
5286-
* on the relcache entries that it processes. We cannot use the
5287-
* subtransaction's resource owner anymore, because we've already
5288-
* started releasing it. But we can use the parent resource owner.
5289-
*/
5290-
CurrentResourceOwner = s->parent->curTransactionOwner;
5291-
52925282
AtEOSubXact_Inval(false);
5293-
5294-
CurrentResourceOwner = s->curTransactionOwner;
5295-
52965283
ResourceOwnerRelease(s->curTransactionOwner,
52975284
RESOURCE_RELEASE_LOCKS,
52985285
false, false);

src/backend/utils/cache/relcache.c

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ static HTAB *OpClassCache = NULL;
275275

276276
static void RelationCloseCleanup(Relation relation);
277277
static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
278+
static void RelationInvalidateRelation(Relation relation);
278279
static void RelationClearRelation(Relation relation, bool rebuild);
279280

280281
static void RelationReloadIndexInfo(Relation relation);
@@ -2512,6 +2513,31 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
25122513
pfree(relation);
25132514
}
25142515

2516+
/*
2517+
* RelationInvalidateRelation - mark a relation cache entry as invalid
2518+
*
2519+
* An entry that's marked as invalid will be reloaded on next access.
2520+
*/
2521+
static void
2522+
RelationInvalidateRelation(Relation relation)
2523+
{
2524+
/*
2525+
* Make sure smgr and lower levels close the relation's files, if they
2526+
* weren't closed already. If the relation is not getting deleted, the
2527+
* next smgr access should reopen the files automatically. This ensures
2528+
* that the low-level file access state is updated after, say, a vacuum
2529+
* truncation.
2530+
*/
2531+
RelationCloseSmgr(relation);
2532+
2533+
/* Free AM cached data, if any */
2534+
if (relation->rd_amcache)
2535+
pfree(relation->rd_amcache);
2536+
relation->rd_amcache = NULL;
2537+
2538+
relation->rd_isvalid = false;
2539+
}
2540+
25152541
/*
25162542
* RelationClearRelation
25172543
*
@@ -2846,14 +2872,28 @@ RelationFlushRelation(Relation relation)
28462872
* New relcache entries are always rebuilt, not flushed; else we'd
28472873
* forget the "new" status of the relation. Ditto for the
28482874
* new-relfilenumber status.
2849-
*
2850-
* The rel could have zero refcnt here, so temporarily increment the
2851-
* refcnt to ensure it's safe to rebuild it. We can assume that the
2852-
* current transaction has some lock on the rel already.
28532875
*/
2854-
RelationIncrementReferenceCount(relation);
2855-
RelationClearRelation(relation, true);
2856-
RelationDecrementReferenceCount(relation);
2876+
if (IsTransactionState() && relation->rd_droppedSubid == InvalidSubTransactionId)
2877+
{
2878+
/*
2879+
* The rel could have zero refcnt here, so temporarily increment
2880+
* the refcnt to ensure it's safe to rebuild it. We can assume
2881+
* that the current transaction has some lock on the rel already.
2882+
*/
2883+
RelationIncrementReferenceCount(relation);
2884+
RelationClearRelation(relation, true);
2885+
RelationDecrementReferenceCount(relation);
2886+
}
2887+
else
2888+
{
2889+
/*
2890+
* During abort processing, the current resource owner is not
2891+
* valid and we cannot hold a refcnt. Without a valid
2892+
* transaction, RelationClearRelation() would just mark the rel as
2893+
* invalid anyway, so we can do the same directly.
2894+
*/
2895+
RelationInvalidateRelation(relation);
2896+
}
28572897
}
28582898
else
28592899
{

0 commit comments

Comments
 (0)