Skip to content

Commit 9923764

Browse files
committed
Fix tupdesc lifespan bug with AfterTriggersTableData.storeslot.
Commit 25936fd adjusted things so that the "storeslot" we use for remapping trigger tuples would have adequate lifespan, but it neglected to consider the lifespan of the tuple descriptor that the slot depends on. It turns out that in at least some cases, the tupdesc we are passing is a refcounted tupdesc, and the refcount for the slot's reference can get assigned to a resource owner having different lifespan than the slot does. That leads to an error like "tupdesc reference 0x7fdef236a1b8 is not owned by resource owner SubTransaction". Worse, because of a second oversight in the same commit, we'd try to free the same tupdesc refcount again while cleaning up after that error, leading to recursive errors and an "ERRORDATA_STACK_SIZE exceeded" PANIC. To fix the initial problem, let's just make a non-refcounted copy of the tupdesc we're supposed to use. That seems likely to guard against additional problems, since there's no strong reason for this code to assume that what it's given is a refcounted tupdesc; in which case there's an independent hazard of the tupdesc having shorter lifespan than the slot does. (I didn't bother trying to free said copy, since it should go away anyway when the (sub) transaction context is cleaned up.) The other issue can be fixed by making the code added to AfterTriggerFreeQuery work like the rest of that function, ie be sure that it doesn't try to free the same slot twice in the event of recursive error cleanup. While here, also clean up minor stylistic issues in the test case added by 25936fd: don't use "create or replace function", as any name collision within the tests is likely to have ill effects that that won't mask; and don't use function names as generic as trigger_function1, especially if you're not going to drop them at the end of the test stanza. Per bug #17607 from Thomas Mc Kay. Back-patch to v12, as the previous fix was. Discussion: https://postgr.es/m/17607-bd8ccc81226f7f80@postgresql.org
1 parent f2094c7 commit 9923764

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

src/backend/commands/trigger.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4419,11 +4419,13 @@ GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
44194419
MemoryContext oldcxt;
44204420

44214421
/*
4422-
* We only need this slot only until AfterTriggerEndQuery, but making
4423-
* it last till end-of-subxact is good enough. It'll be freed by
4424-
* AfterTriggerFreeQuery().
4422+
* We need this slot only until AfterTriggerEndQuery, but making it
4423+
* last till end-of-subxact is good enough. It'll be freed by
4424+
* AfterTriggerFreeQuery(). However, the passed-in tupdesc might have
4425+
* a different lifespan, so we'd better make a copy of that.
44254426
*/
44264427
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
4428+
tupdesc = CreateTupleDescCopy(tupdesc);
44274429
table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
44284430
MemoryContextSwitchTo(oldcxt);
44294431
}
@@ -4720,7 +4722,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
47204722
if (ts)
47214723
tuplestore_end(ts);
47224724
if (table->storeslot)
4723-
ExecDropSingleTupleTableSlot(table->storeslot);
4725+
{
4726+
TupleTableSlot *slot = table->storeslot;
4727+
4728+
table->storeslot = NULL;
4729+
ExecDropSingleTupleTableSlot(slot);
4730+
}
47244731
}
47254732

47264733
/*

src/test/regress/expected/triggers.out

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,7 +3394,7 @@ insert into convslot_test_parent(col1) values ('1');
33943394
insert into convslot_test_child(col1) values ('1');
33953395
insert into convslot_test_parent(col1) values ('3');
33963396
insert into convslot_test_child(col1) values ('3');
3397-
create or replace function trigger_function1()
3397+
create function convslot_trig1()
33983398
returns trigger
33993399
language plpgsql
34003400
AS $$
@@ -3404,7 +3404,7 @@ raise notice 'trigger = %, old_table = %',
34043404
(select string_agg(old_table::text, ', ' order by col1) from old_table);
34053405
return null;
34063406
end; $$;
3407-
create or replace function trigger_function2()
3407+
create function convslot_trig2()
34083408
returns trigger
34093409
language plpgsql
34103410
AS $$
@@ -3416,10 +3416,10 @@ return null;
34163416
end; $$;
34173417
create trigger but_trigger after update on convslot_test_child
34183418
referencing new table as new_table
3419-
for each statement execute function trigger_function2();
3419+
for each statement execute function convslot_trig2();
34203420
update convslot_test_parent set col1 = col1 || '1';
34213421
NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
3422-
create or replace function trigger_function3()
3422+
create function convslot_trig3()
34233423
returns trigger
34243424
language plpgsql
34253425
AS $$
@@ -3432,13 +3432,39 @@ return null;
34323432
end; $$;
34333433
create trigger but_trigger2 after update on convslot_test_child
34343434
referencing old table as old_table new table as new_table
3435-
for each statement execute function trigger_function3();
3435+
for each statement execute function convslot_trig3();
34363436
update convslot_test_parent set col1 = col1 || '1';
34373437
NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
34383438
NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
34393439
create trigger bdt_trigger after delete on convslot_test_child
34403440
referencing old table as old_table
3441-
for each statement execute function trigger_function1();
3441+
for each statement execute function convslot_trig1();
34423442
delete from convslot_test_parent;
34433443
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
34443444
drop table convslot_test_child, convslot_test_parent;
3445+
drop function convslot_trig1();
3446+
drop function convslot_trig2();
3447+
drop function convslot_trig3();
3448+
-- Bug #17607: variant of above in which trigger function raises an error;
3449+
-- we don't see any ill effects unless trigger tuple requires mapping
3450+
create table convslot_test_parent (id int primary key, val int)
3451+
partition by range (id);
3452+
create table convslot_test_part (val int, id int not null);
3453+
alter table convslot_test_parent
3454+
attach partition convslot_test_part for values from (1) to (1000);
3455+
create function convslot_trig4() returns trigger as
3456+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
3457+
create trigger convslot_test_parent_update
3458+
after update on convslot_test_parent
3459+
referencing old table as old_rows new table as new_rows
3460+
for each statement execute procedure convslot_trig4();
3461+
insert into convslot_test_parent (id, val) values (1, 2);
3462+
begin;
3463+
savepoint svp;
3464+
update convslot_test_parent set val = 3; -- error expected
3465+
ERROR: BOOM!
3466+
CONTEXT: PL/pgSQL function convslot_trig4() line 1 at RAISE
3467+
rollback to savepoint svp;
3468+
rollback;
3469+
drop table convslot_test_parent;
3470+
drop function convslot_trig4();

src/test/regress/sql/triggers.sql

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2535,7 +2535,7 @@ insert into convslot_test_child(col1) values ('1');
25352535
insert into convslot_test_parent(col1) values ('3');
25362536
insert into convslot_test_child(col1) values ('3');
25372537

2538-
create or replace function trigger_function1()
2538+
create function convslot_trig1()
25392539
returns trigger
25402540
language plpgsql
25412541
AS $$
@@ -2546,7 +2546,7 @@ raise notice 'trigger = %, old_table = %',
25462546
return null;
25472547
end; $$;
25482548

2549-
create or replace function trigger_function2()
2549+
create function convslot_trig2()
25502550
returns trigger
25512551
language plpgsql
25522552
AS $$
@@ -2559,11 +2559,11 @@ end; $$;
25592559

25602560
create trigger but_trigger after update on convslot_test_child
25612561
referencing new table as new_table
2562-
for each statement execute function trigger_function2();
2562+
for each statement execute function convslot_trig2();
25632563

25642564
update convslot_test_parent set col1 = col1 || '1';
25652565

2566-
create or replace function trigger_function3()
2566+
create function convslot_trig3()
25672567
returns trigger
25682568
language plpgsql
25692569
AS $$
@@ -2577,12 +2577,45 @@ end; $$;
25772577

25782578
create trigger but_trigger2 after update on convslot_test_child
25792579
referencing old table as old_table new table as new_table
2580-
for each statement execute function trigger_function3();
2580+
for each statement execute function convslot_trig3();
25812581
update convslot_test_parent set col1 = col1 || '1';
25822582

25832583
create trigger bdt_trigger after delete on convslot_test_child
25842584
referencing old table as old_table
2585-
for each statement execute function trigger_function1();
2585+
for each statement execute function convslot_trig1();
25862586
delete from convslot_test_parent;
25872587

25882588
drop table convslot_test_child, convslot_test_parent;
2589+
drop function convslot_trig1();
2590+
drop function convslot_trig2();
2591+
drop function convslot_trig3();
2592+
2593+
-- Bug #17607: variant of above in which trigger function raises an error;
2594+
-- we don't see any ill effects unless trigger tuple requires mapping
2595+
2596+
create table convslot_test_parent (id int primary key, val int)
2597+
partition by range (id);
2598+
2599+
create table convslot_test_part (val int, id int not null);
2600+
2601+
alter table convslot_test_parent
2602+
attach partition convslot_test_part for values from (1) to (1000);
2603+
2604+
create function convslot_trig4() returns trigger as
2605+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
2606+
2607+
create trigger convslot_test_parent_update
2608+
after update on convslot_test_parent
2609+
referencing old table as old_rows new table as new_rows
2610+
for each statement execute procedure convslot_trig4();
2611+
2612+
insert into convslot_test_parent (id, val) values (1, 2);
2613+
2614+
begin;
2615+
savepoint svp;
2616+
update convslot_test_parent set val = 3; -- error expected
2617+
rollback to savepoint svp;
2618+
rollback;
2619+
2620+
drop table convslot_test_parent;
2621+
drop function convslot_trig4();

0 commit comments

Comments
 (0)