Skip to content

Commit 216f9c1

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 1d2fec9 commit 216f9c1

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

src/backend/commands/trigger.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -4775,11 +4775,13 @@ GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
47754775
MemoryContext oldcxt;
47764776

47774777
/*
4778-
* We only need this slot only until AfterTriggerEndQuery, but making
4779-
* it last till end-of-subxact is good enough. It'll be freed by
4780-
* AfterTriggerFreeQuery().
4778+
* We need this slot only until AfterTriggerEndQuery, but making it
4779+
* last till end-of-subxact is good enough. It'll be freed by
4780+
* AfterTriggerFreeQuery(). However, the passed-in tupdesc might have
4781+
* a different lifespan, so we'd better make a copy of that.
47814782
*/
47824783
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
4784+
tupdesc = CreateTupleDescCopy(tupdesc);
47834785
table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
47844786
MemoryContextSwitchTo(oldcxt);
47854787
}
@@ -5098,7 +5100,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
50985100
if (ts)
50995101
tuplestore_end(ts);
51005102
if (table->storeslot)
5101-
ExecDropSingleTupleTableSlot(table->storeslot);
5103+
{
5104+
TupleTableSlot *slot = table->storeslot;
5105+
5106+
table->storeslot = NULL;
5107+
ExecDropSingleTupleTableSlot(slot);
5108+
}
51025109
}
51035110

51045111
/*

src/test/regress/expected/triggers.out

+32-6
Original file line numberDiff line numberDiff line change
@@ -3468,7 +3468,7 @@ insert into convslot_test_parent(col1) values ('1');
34683468
insert into convslot_test_child(col1) values ('1');
34693469
insert into convslot_test_parent(col1) values ('3');
34703470
insert into convslot_test_child(col1) values ('3');
3471-
create or replace function trigger_function1()
3471+
create function convslot_trig1()
34723472
returns trigger
34733473
language plpgsql
34743474
AS $$
@@ -3478,7 +3478,7 @@ raise notice 'trigger = %, old_table = %',
34783478
(select string_agg(old_table::text, ', ' order by col1) from old_table);
34793479
return null;
34803480
end; $$;
3481-
create or replace function trigger_function2()
3481+
create function convslot_trig2()
34823482
returns trigger
34833483
language plpgsql
34843484
AS $$
@@ -3490,10 +3490,10 @@ return null;
34903490
end; $$;
34913491
create trigger but_trigger after update on convslot_test_child
34923492
referencing new table as new_table
3493-
for each statement execute function trigger_function2();
3493+
for each statement execute function convslot_trig2();
34943494
update convslot_test_parent set col1 = col1 || '1';
34953495
NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
3496-
create or replace function trigger_function3()
3496+
create function convslot_trig3()
34973497
returns trigger
34983498
language plpgsql
34993499
AS $$
@@ -3506,16 +3506,42 @@ return null;
35063506
end; $$;
35073507
create trigger but_trigger2 after update on convslot_test_child
35083508
referencing old table as old_table new table as new_table
3509-
for each statement execute function trigger_function3();
3509+
for each statement execute function convslot_trig3();
35103510
update convslot_test_parent set col1 = col1 || '1';
35113511
NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
35123512
NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
35133513
create trigger bdt_trigger after delete on convslot_test_child
35143514
referencing old table as old_table
3515-
for each statement execute function trigger_function1();
3515+
for each statement execute function convslot_trig1();
35163516
delete from convslot_test_parent;
35173517
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
35183518
drop table convslot_test_child, convslot_test_parent;
3519+
drop function convslot_trig1();
3520+
drop function convslot_trig2();
3521+
drop function convslot_trig3();
3522+
-- Bug #17607: variant of above in which trigger function raises an error;
3523+
-- we don't see any ill effects unless trigger tuple requires mapping
3524+
create table convslot_test_parent (id int primary key, val int)
3525+
partition by range (id);
3526+
create table convslot_test_part (val int, id int not null);
3527+
alter table convslot_test_parent
3528+
attach partition convslot_test_part for values from (1) to (1000);
3529+
create function convslot_trig4() returns trigger as
3530+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
3531+
create trigger convslot_test_parent_update
3532+
after update on convslot_test_parent
3533+
referencing old table as old_rows new table as new_rows
3534+
for each statement execute procedure convslot_trig4();
3535+
insert into convslot_test_parent (id, val) values (1, 2);
3536+
begin;
3537+
savepoint svp;
3538+
update convslot_test_parent set val = 3; -- error expected
3539+
ERROR: BOOM!
3540+
CONTEXT: PL/pgSQL function convslot_trig4() line 1 at RAISE
3541+
rollback to savepoint svp;
3542+
rollback;
3543+
drop table convslot_test_parent;
3544+
drop function convslot_trig4();
35193545
-- Test trigger renaming on partitioned tables
35203546
create table grandparent (id int, primary key (id)) partition by range (id);
35213547
create table middle partition of grandparent for values from (1) to (10)

src/test/regress/sql/triggers.sql

+39-6
Original file line numberDiff line numberDiff line change
@@ -2615,7 +2615,7 @@ insert into convslot_test_child(col1) values ('1');
26152615
insert into convslot_test_parent(col1) values ('3');
26162616
insert into convslot_test_child(col1) values ('3');
26172617

2618-
create or replace function trigger_function1()
2618+
create function convslot_trig1()
26192619
returns trigger
26202620
language plpgsql
26212621
AS $$
@@ -2626,7 +2626,7 @@ raise notice 'trigger = %, old_table = %',
26262626
return null;
26272627
end; $$;
26282628

2629-
create or replace function trigger_function2()
2629+
create function convslot_trig2()
26302630
returns trigger
26312631
language plpgsql
26322632
AS $$
@@ -2639,11 +2639,11 @@ end; $$;
26392639

26402640
create trigger but_trigger after update on convslot_test_child
26412641
referencing new table as new_table
2642-
for each statement execute function trigger_function2();
2642+
for each statement execute function convslot_trig2();
26432643

26442644
update convslot_test_parent set col1 = col1 || '1';
26452645

2646-
create or replace function trigger_function3()
2646+
create function convslot_trig3()
26472647
returns trigger
26482648
language plpgsql
26492649
AS $$
@@ -2657,15 +2657,48 @@ end; $$;
26572657

26582658
create trigger but_trigger2 after update on convslot_test_child
26592659
referencing old table as old_table new table as new_table
2660-
for each statement execute function trigger_function3();
2660+
for each statement execute function convslot_trig3();
26612661
update convslot_test_parent set col1 = col1 || '1';
26622662

26632663
create trigger bdt_trigger after delete on convslot_test_child
26642664
referencing old table as old_table
2665-
for each statement execute function trigger_function1();
2665+
for each statement execute function convslot_trig1();
26662666
delete from convslot_test_parent;
26672667

26682668
drop table convslot_test_child, convslot_test_parent;
2669+
drop function convslot_trig1();
2670+
drop function convslot_trig2();
2671+
drop function convslot_trig3();
2672+
2673+
-- Bug #17607: variant of above in which trigger function raises an error;
2674+
-- we don't see any ill effects unless trigger tuple requires mapping
2675+
2676+
create table convslot_test_parent (id int primary key, val int)
2677+
partition by range (id);
2678+
2679+
create table convslot_test_part (val int, id int not null);
2680+
2681+
alter table convslot_test_parent
2682+
attach partition convslot_test_part for values from (1) to (1000);
2683+
2684+
create function convslot_trig4() returns trigger as
2685+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
2686+
2687+
create trigger convslot_test_parent_update
2688+
after update on convslot_test_parent
2689+
referencing old table as old_rows new table as new_rows
2690+
for each statement execute procedure convslot_trig4();
2691+
2692+
insert into convslot_test_parent (id, val) values (1, 2);
2693+
2694+
begin;
2695+
savepoint svp;
2696+
update convslot_test_parent set val = 3; -- error expected
2697+
rollback to savepoint svp;
2698+
rollback;
2699+
2700+
drop table convslot_test_parent;
2701+
drop function convslot_trig4();
26692702

26702703
-- Test trigger renaming on partitioned tables
26712704
create table grandparent (id int, primary key (id)) partition by range (id);

0 commit comments

Comments
 (0)