summaryrefslogtreecommitdiff
path: root/src/test/isolation
diff options
context:
space:
mode:
authorTom Lane2021-05-20 22:32:37 +0000
committerTom Lane2021-05-20 22:32:37 +0000
commitf21fadafaf0fb5ea4c9622d915972651273d62ce (patch)
tree3ebcc8a234a9dbe43b2ddb8184d4b486c19da700 /src/test/isolation
parent4f586fe244a296d7c781de3f06c54755f2ae222b (diff)
Avoid detoasting failure after COMMIT inside a plpgsql FOR loop.
exec_for_query() normally tries to prefetch a few rows at a time from the query being iterated over, so as to reduce executor entry/exit overhead. Unfortunately this is unsafe if we have COMMIT or ROLLBACK within the loop, because there might be TOAST references in the data that we prefetched but haven't yet examined. Immediately after the COMMIT/ROLLBACK, we have no snapshots in the session, meaning that VACUUM is at liberty to remove recently-deleted TOAST rows. This was originally reported as a case triggering the "no known snapshots" error in init_toast_snapshot(), but even if you miss hitting that, you can get "missing toast chunk", as illustrated by the added isolation test case. To fix, just disable prefetching in non-atomic contexts. Maybe there will be performance complaints prompting us to work harder later, but it's not clear at the moment that this really costs much, and I doubt we'd want to back-patch any complicated fix. In passing, adjust that error message in init_toast_snapshot() to be a little clearer about the likely cause of the problem. Patch by me, based on earlier investigation by Konstantin Knizhnik. Per bug #15990 from Andreas Wicht. Back-patch to v11 where intra-procedure COMMIT was added. Discussion: https://postgr.es/m/15990-eee2ac466b11293d@postgresql.org
Diffstat (limited to 'src/test/isolation')
-rw-r--r--src/test/isolation/expected/plpgsql-toast.out43
-rw-r--r--src/test/isolation/specs/plpgsql-toast.spec20
2 files changed, 63 insertions, 0 deletions
diff --git a/src/test/isolation/expected/plpgsql-toast.out b/src/test/isolation/expected/plpgsql-toast.out
index fc557da5e77..4f216b94b62 100644
--- a/src/test/isolation/expected/plpgsql-toast.out
+++ b/src/test/isolation/expected/plpgsql-toast.out
@@ -192,3 +192,46 @@ pg_advisory_unlock
t
s1: NOTICE: length(r) = 6002
step assign5: <... completed>
+
+starting permutation: lock assign6 vacuum unlock
+pg_advisory_unlock_all
+
+
+pg_advisory_unlock_all
+
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+
+
+step assign6:
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+
+t
+s1: NOTICE: length(r) = 6002
+s1: NOTICE: length(r) = 9002
+s1: NOTICE: length(r) = 12002
+step assign6: <... completed>
diff --git a/src/test/isolation/specs/plpgsql-toast.spec b/src/test/isolation/specs/plpgsql-toast.spec
index fe7090addbb..d360f8fccbf 100644
--- a/src/test/isolation/specs/plpgsql-toast.spec
+++ b/src/test/isolation/specs/plpgsql-toast.spec
@@ -112,6 +112,25 @@ do $$
$$;
}
+# FOR loop must not hold any fetched-but-not-detoasted values across commit
+step "assign6"
+{
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+}
+
session "s2"
setup
{
@@ -135,3 +154,4 @@ permutation "lock" "assign2" "vacuum" "unlock"
permutation "lock" "assign3" "vacuum" "unlock"
permutation "lock" "assign4" "vacuum" "unlock"
permutation "lock" "assign5" "vacuum" "unlock"
+permutation "lock" "assign6" "vacuum" "unlock"