Skip to content

Commit 7b5d4c2

Browse files
committed
Fix Portal snapshot tracking to handle subtransactions properly.
Commit 84f5c29 forgot to consider the possibility that EnsurePortalSnapshotExists could run inside a subtransaction with lifespan shorter than the Portal's. In that case, the new active snapshot would be popped at the end of the subtransaction, leaving a dangling pointer in the Portal, with mayhem ensuing. To fix, make sure the ActiveSnapshot stack entry is marked with the same subtransaction nesting level as the associated Portal. It's certainly safe to do so since we won't be here at all unless the stack is empty; hence we can't create an out-of-order stack. Let's also apply this logic in the case where PortalRunUtility sets portalSnapshot, just to be sure that path can't cause similar problems. It's slightly less clear that that path can't create an out-of-order stack, so add an assertion guarding it. Report and patch by Bertrand Drouvot (with kibitzing by me). Back-patch to v11, like the previous commit. Discussion: https://postgr.es/m/[email protected]
1 parent 2d44dee commit 7b5d4c2

File tree

8 files changed

+93
-8
lines changed

8 files changed

+93
-8
lines changed

src/backend/access/transam/xact.c

+1
Original file line numberDiff line numberDiff line change
@@ -4863,6 +4863,7 @@ CommitSubTransaction(void)
48634863
AfterTriggerEndSubXact(true);
48644864
AtSubCommit_Portals(s->subTransactionId,
48654865
s->parent->subTransactionId,
4866+
s->parent->nestingLevel,
48664867
s->parent->curTransactionOwner);
48674868
AtEOSubXact_LargeObject(true, s->subTransactionId,
48684869
s->parent->subTransactionId);

src/backend/tcop/pquery.c

+20-7
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,9 @@ PortalStart(Portal portal, ParamListInfo params,
480480
* We could remember the snapshot in portal->portalSnapshot,
481481
* but presently there seems no need to, as this code path
482482
* cannot be used for non-atomic execution. Hence there can't
483-
* be any commit/abort that might destroy the snapshot.
483+
* be any commit/abort that might destroy the snapshot. Since
484+
* we don't do that, there's also no need to force a
485+
* non-default nesting level for the snapshot.
484486
*/
485487

486488
/*
@@ -1136,9 +1138,15 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
11361138
snapshot = RegisterSnapshot(snapshot);
11371139
portal->holdSnapshot = snapshot;
11381140
}
1139-
/* In any case, make the snapshot active and remember it in portal */
1140-
PushActiveSnapshot(snapshot);
1141-
/* PushActiveSnapshot might have copied the snapshot */
1141+
1142+
/*
1143+
* In any case, make the snapshot active and remember it in portal.
1144+
* Because the portal now references the snapshot, we must tell
1145+
* snapmgr.c that the snapshot belongs to the portal's transaction
1146+
* level, else we risk portalSnapshot becoming a dangling pointer.
1147+
*/
1148+
PushActiveSnapshotWithLevel(snapshot, portal->createLevel);
1149+
/* PushActiveSnapshotWithLevel might have copied the snapshot */
11421150
portal->portalSnapshot = GetActiveSnapshot();
11431151
}
11441152
else
@@ -1784,8 +1792,13 @@ EnsurePortalSnapshotExists(void)
17841792
elog(ERROR, "cannot execute SQL without an outer snapshot or portal");
17851793
Assert(portal->portalSnapshot == NULL);
17861794

1787-
/* Create a new snapshot and make it active */
1788-
PushActiveSnapshot(GetTransactionSnapshot());
1789-
/* PushActiveSnapshot might have copied the snapshot */
1795+
/*
1796+
* Create a new snapshot, make it active, and remember it in portal.
1797+
* Because the portal now references the snapshot, we must tell snapmgr.c
1798+
* that the snapshot belongs to the portal's transaction level, else we
1799+
* risk portalSnapshot becoming a dangling pointer.
1800+
*/
1801+
PushActiveSnapshotWithLevel(GetTransactionSnapshot(), portal->createLevel);
1802+
/* PushActiveSnapshotWithLevel might have copied the snapshot */
17901803
portal->portalSnapshot = GetActiveSnapshot();
17911804
}

src/backend/utils/mmgr/portalmem.c

+4
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent)
210210
portal->cleanup = PortalCleanup;
211211
portal->createSubid = GetCurrentSubTransactionId();
212212
portal->activeSubid = portal->createSubid;
213+
portal->createLevel = GetCurrentTransactionNestLevel();
213214
portal->strategy = PORTAL_MULTI_QUERY;
214215
portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
215216
portal->atStart = true;
@@ -657,6 +658,7 @@ HoldPortal(Portal portal)
657658
*/
658659
portal->createSubid = InvalidSubTransactionId;
659660
portal->activeSubid = InvalidSubTransactionId;
661+
portal->createLevel = 0;
660662
}
661663

662664
/*
@@ -940,6 +942,7 @@ PortalErrorCleanup(void)
940942
void
941943
AtSubCommit_Portals(SubTransactionId mySubid,
942944
SubTransactionId parentSubid,
945+
int parentLevel,
943946
ResourceOwner parentXactOwner)
944947
{
945948
HASH_SEQ_STATUS status;
@@ -954,6 +957,7 @@ AtSubCommit_Portals(SubTransactionId mySubid,
954957
if (portal->createSubid == mySubid)
955958
{
956959
portal->createSubid = parentSubid;
960+
portal->createLevel = parentLevel;
957961
if (portal->resowner)
958962
ResourceOwnerNewParent(portal->resowner, parentXactOwner);
959963
}

src/backend/utils/time/snapmgr.c

+16-1
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,25 @@ FreeSnapshot(Snapshot snapshot)
678678
*/
679679
void
680680
PushActiveSnapshot(Snapshot snap)
681+
{
682+
PushActiveSnapshotWithLevel(snap, GetCurrentTransactionNestLevel());
683+
}
684+
685+
/*
686+
* PushActiveSnapshotWithLevel
687+
* Set the given snapshot as the current active snapshot
688+
*
689+
* Same as PushActiveSnapshot except that caller can specify the
690+
* transaction nesting level that "owns" the snapshot. This level
691+
* must not be deeper than the current top of the snapshot stack.
692+
*/
693+
void
694+
PushActiveSnapshotWithLevel(Snapshot snap, int snap_level)
681695
{
682696
ActiveSnapshotElt *newactive;
683697

684698
Assert(snap != InvalidSnapshot);
699+
Assert(ActiveSnapshot == NULL || snap_level >= ActiveSnapshot->as_level);
685700

686701
newactive = MemoryContextAlloc(TopTransactionContext, sizeof(ActiveSnapshotElt));
687702

@@ -695,7 +710,7 @@ PushActiveSnapshot(Snapshot snap)
695710
newactive->as_snap = snap;
696711

697712
newactive->as_next = ActiveSnapshot;
698-
newactive->as_level = GetCurrentTransactionNestLevel();
713+
newactive->as_level = snap_level;
699714

700715
newactive->as_snap->active_count++;
701716

src/include/utils/portal.h

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ typedef struct PortalData
130130
*/
131131
SubTransactionId createSubid; /* the creating subxact */
132132
SubTransactionId activeSubid; /* the last subxact with activity */
133+
int createLevel; /* creating subxact's nesting level */
133134

134135
/* The query or queries the portal will execute */
135136
const char *sourceText; /* text of query (as of 8.4, never NULL) */
@@ -219,6 +220,7 @@ extern void AtCleanup_Portals(void);
219220
extern void PortalErrorCleanup(void);
220221
extern void AtSubCommit_Portals(SubTransactionId mySubid,
221222
SubTransactionId parentSubid,
223+
int parentLevel,
222224
ResourceOwner parentXactOwner);
223225
extern void AtSubAbort_Portals(SubTransactionId mySubid,
224226
SubTransactionId parentSubid,

src/include/utils/snapmgr.h

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ extern void InvalidateCatalogSnapshot(void);
114114
extern void InvalidateCatalogSnapshotConditionally(void);
115115

116116
extern void PushActiveSnapshot(Snapshot snapshot);
117+
extern void PushActiveSnapshotWithLevel(Snapshot snapshot, int snap_level);
117118
extern void PushCopiedSnapshot(Snapshot snapshot);
118119
extern void UpdateActiveSnapshotCommandId(void);
119120
extern void PopActiveSnapshot(void);

src/pl/plpgsql/src/expected/plpgsql_transaction.out

+28
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,34 @@ SELECT * FROM test1;
430430
---+---
431431
(0 rows)
432432

433+
-- test commit/rollback inside exception handler, too
434+
TRUNCATE test1;
435+
DO LANGUAGE plpgsql $$
436+
BEGIN
437+
FOR i IN 1..10 LOOP
438+
BEGIN
439+
INSERT INTO test1 VALUES (i, 'good');
440+
INSERT INTO test1 VALUES (i/0, 'bad');
441+
EXCEPTION
442+
WHEN division_by_zero THEN
443+
INSERT INTO test1 VALUES (i, 'exception');
444+
IF (i % 3) > 0 THEN COMMIT; ELSE ROLLBACK; END IF;
445+
END;
446+
END LOOP;
447+
END;
448+
$$;
449+
SELECT * FROM test1;
450+
a | b
451+
----+-----------
452+
1 | exception
453+
2 | exception
454+
4 | exception
455+
5 | exception
456+
7 | exception
457+
8 | exception
458+
10 | exception
459+
(7 rows)
460+
433461
-- detoast result of simple expression after commit
434462
CREATE TEMP TABLE test4(f1 text);
435463
ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression

src/pl/plpgsql/src/sql/plpgsql_transaction.sql

+21
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,27 @@ $$;
354354
SELECT * FROM test1;
355355

356356

357+
-- test commit/rollback inside exception handler, too
358+
TRUNCATE test1;
359+
360+
DO LANGUAGE plpgsql $$
361+
BEGIN
362+
FOR i IN 1..10 LOOP
363+
BEGIN
364+
INSERT INTO test1 VALUES (i, 'good');
365+
INSERT INTO test1 VALUES (i/0, 'bad');
366+
EXCEPTION
367+
WHEN division_by_zero THEN
368+
INSERT INTO test1 VALUES (i, 'exception');
369+
IF (i % 3) > 0 THEN COMMIT; ELSE ROLLBACK; END IF;
370+
END;
371+
END LOOP;
372+
END;
373+
$$;
374+
375+
SELECT * FROM test1;
376+
377+
357378
-- detoast result of simple expression after commit
358379
CREATE TEMP TABLE test4(f1 text);
359380
ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression

0 commit comments

Comments
 (0)