Skip to content

Commit 5197630

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 0a64835 commit 5197630

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
@@ -4711,11 +4711,13 @@ GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
47114711
MemoryContext oldcxt;
47124712

47134713
/*
4714-
* We only need this slot only until AfterTriggerEndQuery, but making
4715-
* it last till end-of-subxact is good enough. It'll be freed by
4716-
* AfterTriggerFreeQuery().
4714+
* We need this slot only until AfterTriggerEndQuery, but making it
4715+
* last till end-of-subxact is good enough. It'll be freed by
4716+
* AfterTriggerFreeQuery(). However, the passed-in tupdesc might have
4717+
* a different lifespan, so we'd better make a copy of that.
47174718
*/
47184719
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
4720+
tupdesc = CreateTupleDescCopy(tupdesc);
47194721
table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
47204722
MemoryContextSwitchTo(oldcxt);
47214723
}
@@ -5011,7 +5013,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
50115013
if (ts)
50125014
tuplestore_end(ts);
50135015
if (table->storeslot)
5014-
ExecDropSingleTupleTableSlot(table->storeslot);
5016+
{
5017+
TupleTableSlot *slot = table->storeslot;
5018+
5019+
table->storeslot = NULL;
5020+
ExecDropSingleTupleTableSlot(slot);
5021+
}
50155022
}
50165023

50175024
/*

src/test/regress/expected/triggers.out

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,7 +3182,7 @@ insert into convslot_test_parent(col1) values ('1');
31823182
insert into convslot_test_child(col1) values ('1');
31833183
insert into convslot_test_parent(col1) values ('3');
31843184
insert into convslot_test_child(col1) values ('3');
3185-
create or replace function trigger_function1()
3185+
create function convslot_trig1()
31863186
returns trigger
31873187
language plpgsql
31883188
AS $$
@@ -3192,7 +3192,7 @@ raise notice 'trigger = %, old_table = %',
31923192
(select string_agg(old_table::text, ', ' order by col1) from old_table);
31933193
return null;
31943194
end; $$;
3195-
create or replace function trigger_function2()
3195+
create function convslot_trig2()
31963196
returns trigger
31973197
language plpgsql
31983198
AS $$
@@ -3204,10 +3204,10 @@ return null;
32043204
end; $$;
32053205
create trigger but_trigger after update on convslot_test_child
32063206
referencing new table as new_table
3207-
for each statement execute function trigger_function2();
3207+
for each statement execute function convslot_trig2();
32083208
update convslot_test_parent set col1 = col1 || '1';
32093209
NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
3210-
create or replace function trigger_function3()
3210+
create function convslot_trig3()
32113211
returns trigger
32123212
language plpgsql
32133213
AS $$
@@ -3220,13 +3220,39 @@ return null;
32203220
end; $$;
32213221
create trigger but_trigger2 after update on convslot_test_child
32223222
referencing old table as old_table new table as new_table
3223-
for each statement execute function trigger_function3();
3223+
for each statement execute function convslot_trig3();
32243224
update convslot_test_parent set col1 = col1 || '1';
32253225
NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
32263226
NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
32273227
create trigger bdt_trigger after delete on convslot_test_child
32283228
referencing old table as old_table
3229-
for each statement execute function trigger_function1();
3229+
for each statement execute function convslot_trig1();
32303230
delete from convslot_test_parent;
32313231
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
32323232
drop table convslot_test_child, convslot_test_parent;
3233+
drop function convslot_trig1();
3234+
drop function convslot_trig2();
3235+
drop function convslot_trig3();
3236+
-- Bug #17607: variant of above in which trigger function raises an error;
3237+
-- we don't see any ill effects unless trigger tuple requires mapping
3238+
create table convslot_test_parent (id int primary key, val int)
3239+
partition by range (id);
3240+
create table convslot_test_part (val int, id int not null);
3241+
alter table convslot_test_parent
3242+
attach partition convslot_test_part for values from (1) to (1000);
3243+
create function convslot_trig4() returns trigger as
3244+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
3245+
create trigger convslot_test_parent_update
3246+
after update on convslot_test_parent
3247+
referencing old table as old_rows new table as new_rows
3248+
for each statement execute procedure convslot_trig4();
3249+
insert into convslot_test_parent (id, val) values (1, 2);
3250+
begin;
3251+
savepoint svp;
3252+
update convslot_test_parent set val = 3; -- error expected
3253+
ERROR: BOOM!
3254+
CONTEXT: PL/pgSQL function convslot_trig4() line 1 at RAISE
3255+
rollback to savepoint svp;
3256+
rollback;
3257+
drop table convslot_test_parent;
3258+
drop function convslot_trig4();

src/test/regress/sql/triggers.sql

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,7 +2366,7 @@ insert into convslot_test_child(col1) values ('1');
23662366
insert into convslot_test_parent(col1) values ('3');
23672367
insert into convslot_test_child(col1) values ('3');
23682368

2369-
create or replace function trigger_function1()
2369+
create function convslot_trig1()
23702370
returns trigger
23712371
language plpgsql
23722372
AS $$
@@ -2377,7 +2377,7 @@ raise notice 'trigger = %, old_table = %',
23772377
return null;
23782378
end; $$;
23792379

2380-
create or replace function trigger_function2()
2380+
create function convslot_trig2()
23812381
returns trigger
23822382
language plpgsql
23832383
AS $$
@@ -2390,11 +2390,11 @@ end; $$;
23902390

23912391
create trigger but_trigger after update on convslot_test_child
23922392
referencing new table as new_table
2393-
for each statement execute function trigger_function2();
2393+
for each statement execute function convslot_trig2();
23942394

23952395
update convslot_test_parent set col1 = col1 || '1';
23962396

2397-
create or replace function trigger_function3()
2397+
create function convslot_trig3()
23982398
returns trigger
23992399
language plpgsql
24002400
AS $$
@@ -2408,12 +2408,45 @@ end; $$;
24082408

24092409
create trigger but_trigger2 after update on convslot_test_child
24102410
referencing old table as old_table new table as new_table
2411-
for each statement execute function trigger_function3();
2411+
for each statement execute function convslot_trig3();
24122412
update convslot_test_parent set col1 = col1 || '1';
24132413

24142414
create trigger bdt_trigger after delete on convslot_test_child
24152415
referencing old table as old_table
2416-
for each statement execute function trigger_function1();
2416+
for each statement execute function convslot_trig1();
24172417
delete from convslot_test_parent;
24182418

24192419
drop table convslot_test_child, convslot_test_parent;
2420+
drop function convslot_trig1();
2421+
drop function convslot_trig2();
2422+
drop function convslot_trig3();
2423+
2424+
-- Bug #17607: variant of above in which trigger function raises an error;
2425+
-- we don't see any ill effects unless trigger tuple requires mapping
2426+
2427+
create table convslot_test_parent (id int primary key, val int)
2428+
partition by range (id);
2429+
2430+
create table convslot_test_part (val int, id int not null);
2431+
2432+
alter table convslot_test_parent
2433+
attach partition convslot_test_part for values from (1) to (1000);
2434+
2435+
create function convslot_trig4() returns trigger as
2436+
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
2437+
2438+
create trigger convslot_test_parent_update
2439+
after update on convslot_test_parent
2440+
referencing old table as old_rows new table as new_rows
2441+
for each statement execute procedure convslot_trig4();
2442+
2443+
insert into convslot_test_parent (id, val) values (1, 2);
2444+
2445+
begin;
2446+
savepoint svp;
2447+
update convslot_test_parent set val = 3; -- error expected
2448+
rollback to savepoint svp;
2449+
rollback;
2450+
2451+
drop table convslot_test_parent;
2452+
drop function convslot_trig4();

0 commit comments

Comments
 (0)