Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7c38ef2

Browse files
committed
Fix temporary object cleanup failing due to toast access without snapshot.
When cleaning up temporary objects during process exit the cleanup could fail with: FATAL: cannot fetch toast data without an active snapshot The bug is caused by RemoveTempRelationsCallback() not setting up a snapshot. If an object with toasted catalog data needs to be cleaned up, init_toast_snapshot() could fail with the above error. Most of the time however the the problem is masked due to cached catalog snapshots being returned by GetOldestSnapshot(). But dropping an object can cause catalog invalidations to be emitted. If no further catalog accesses are necessary between the invalidation processing and the next toast datum deletion, the bug becomes visible. It's easy to miss this bug because it typically happens after clients disconnect and the FATAL error just ends up in the log. Luckily temporary table cleanup at the next use of the same temporary schema or during DISCARD ALL does not have the same problem. Fix the bug by pushing a snapshot in RemoveTempRelationsCallback(). Also add isolation tests for temporary object cleanup, including objects with toasted catalog data. A future HEAD only commit will add an assertion trying to make this more visible. Reported-By: Miles Delahunty Author: Andres Freund Discussion: https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw@mail.gmail.com Backpatch: 10-
1 parent 27b02e0 commit 7c38ef2

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

src/backend/catalog/namespace.c

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "utils/inval.h"
5656
#include "utils/lsyscache.h"
5757
#include "utils/memutils.h"
58+
#include "utils/snapmgr.h"
5859
#include "utils/syscache.h"
5960
#include "utils/varlena.h"
6061

@@ -4292,9 +4293,11 @@ RemoveTempRelationsCallback(int code, Datum arg)
42924293
/* Need to ensure we have a usable transaction. */
42934294
AbortOutOfAnyTransaction();
42944295
StartTransactionCommand();
4296+
PushActiveSnapshot(GetTransactionSnapshot());
42954297

42964298
RemoveTempRelations(myTempNamespace);
42974299

4300+
PopActiveSnapshot();
42984301
CommitTransactionCommand();
42994302
}
43004303
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: s1_create_temp_objects s1_discard_temp s2_check_schema
4+
step s1_create_temp_objects:
5+
6+
-- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
7+
-- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
8+
SELECT exec(format($outer$
9+
CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
10+
(SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
11+
12+
-- The above bug requirs function removal to happen after a catalog
13+
-- invalidation. dependency.c sorts objects in descending oid order so
14+
-- that newer objects are deleted before older objects, so create a
15+
-- table after.
16+
CREATE TEMPORARY TABLE invalidate_catalog_cache();
17+
18+
-- test non-temp function is dropped when depending on temp table
19+
CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
20+
21+
CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
22+
23+
exec
24+
----
25+
26+
(1 row)
27+
28+
step s1_discard_temp:
29+
DISCARD TEMP;
30+
31+
step s2_check_schema:
32+
SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
33+
SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
34+
SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
35+
36+
oid
37+
---
38+
(0 rows)
39+
40+
oid
41+
---
42+
(0 rows)
43+
44+
oid
45+
---
46+
(0 rows)
47+
48+
49+
starting permutation: s1_advisory s2_advisory s1_create_temp_objects s1_exit s2_check_schema
50+
step s1_advisory:
51+
SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
52+
53+
pg_advisory_lock
54+
----------------
55+
56+
(1 row)
57+
58+
step s2_advisory:
59+
SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
60+
<waiting ...>
61+
step s1_create_temp_objects:
62+
63+
-- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
64+
-- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
65+
SELECT exec(format($outer$
66+
CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
67+
(SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
68+
69+
-- The above bug requirs function removal to happen after a catalog
70+
-- invalidation. dependency.c sorts objects in descending oid order so
71+
-- that newer objects are deleted before older objects, so create a
72+
-- table after.
73+
CREATE TEMPORARY TABLE invalidate_catalog_cache();
74+
75+
-- test non-temp function is dropped when depending on temp table
76+
CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
77+
78+
CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
79+
80+
exec
81+
----
82+
83+
(1 row)
84+
85+
step s1_exit:
86+
SELECT pg_terminate_backend(pg_backend_pid());
87+
88+
FATAL: terminating connection due to administrator command
89+
server closed the connection unexpectedly
90+
This probably means the server terminated abnormally
91+
before or while processing the request.
92+
93+
step s2_advisory: <... completed>
94+
pg_advisory_lock
95+
----------------
96+
97+
(1 row)
98+
99+
step s2_check_schema:
100+
SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
101+
SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
102+
SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
103+
104+
oid
105+
---
106+
(0 rows)
107+
108+
oid
109+
---
110+
(0 rows)
111+
112+
oid
113+
---
114+
(0 rows)
115+

src/test/isolation/isolation_schedule

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ test: eval-plan-qual-trigger
3838
test: lock-update-delete
3939
test: lock-update-traversal
4040
test: inherit-temp
41+
test: temp-schema-cleanup
4142
test: insert-conflict-do-nothing
4243
test: insert-conflict-do-nothing-2
4344
test: insert-conflict-do-update
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Test cleanup of objects in temporary schema.
2+
3+
setup {
4+
CREATE TABLE s1_temp_schema(oid oid);
5+
-- to help create a long function
6+
CREATE FUNCTION exec(p_foo text) RETURNS void LANGUAGE plpgsql AS $$BEGIN EXECUTE p_foo; END;$$;
7+
}
8+
9+
teardown {
10+
DROP TABLE s1_temp_schema;
11+
DROP FUNCTION exec(text);
12+
}
13+
14+
session "s1"
15+
setup {
16+
CREATE TEMPORARY TABLE just_to_create_temp_schema();
17+
DROP TABLE just_to_create_temp_schema;
18+
INSERT INTO s1_temp_schema SELECT pg_my_temp_schema();
19+
}
20+
21+
step s1_advisory {
22+
SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
23+
}
24+
25+
step s1_create_temp_objects {
26+
27+
-- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
28+
-- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
29+
SELECT exec(format($outer$
30+
CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
31+
(SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
32+
33+
-- The above bug requirs function removal to happen after a catalog
34+
-- invalidation. dependency.c sorts objects in descending oid order so
35+
-- that newer objects are deleted before older objects, so create a
36+
-- table after.
37+
CREATE TEMPORARY TABLE invalidate_catalog_cache();
38+
39+
-- test non-temp function is dropped when depending on temp table
40+
CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
41+
42+
CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
43+
}
44+
45+
step s1_discard_temp {
46+
DISCARD TEMP;
47+
}
48+
49+
step s1_exit {
50+
SELECT pg_terminate_backend(pg_backend_pid());
51+
}
52+
53+
54+
session "s2"
55+
56+
step s2_advisory {
57+
SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
58+
}
59+
60+
step s2_check_schema {
61+
SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
62+
SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
63+
SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
64+
}
65+
66+
67+
# Test temporary object cleanup during DISCARD.
68+
permutation
69+
s1_create_temp_objects
70+
s1_discard_temp
71+
s2_check_schema
72+
73+
# Test temporary object cleanup during process exit.
74+
#
75+
# To check (in s2) if temporary objects (in s1) have properly been removed we
76+
# need to wait for s1 to finish cleaning up. Luckily session level advisory
77+
# locks are released only after temp table cleanup.
78+
permutation
79+
s1_advisory
80+
s2_advisory
81+
s1_create_temp_objects
82+
s1_exit
83+
s2_check_schema
84+
85+
# Can't run further tests here, because s1's connection is dead

0 commit comments

Comments
 (0)