Author: Noah Misch Commit: Noah Misch [not for commit] demo pin starvation from autovacuum inplace update Test with intra-grant-inplace-db.spec. Changes here: a. Start intra-grant-inplace-db.spec step vac2 just after some autovacuum worker is blocked on the xid of the uncommitted GRANT. While blocked, the worker holds a pin on the one pg_database page. vac2 needs LockBufferForCleanup(that-page). b. Make LockBufferForCleanup() sleep 10ms after WAIT_EVENT_BUFFER_PIN, to give the next autovacuum worker time to win the pinning race. diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 0f02bf6..5268294 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -5344,8 +5344,17 @@ LockBufferForCleanup(Buffer buffer) SetStartupBufferPinWaitBufId(-1); } else + { ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN); + /* + * Sleep to induce starvation. With heavyweight locks, the + * releaser grants to the next queue member, preventing + * starvation. Buffer pins have no such mechanism. + */ + pg_usleep(10000); + } + /* * Remove flag marking us as waiter. Normally this will not be set * anymore, but ProcWaitForSignal() can return for other signals as diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile index ade2256..ca487de 100644 --- a/src/test/isolation/Makefile +++ b/src/test/isolation/Makefile @@ -64,6 +64,9 @@ installcheck: all check: all $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule +nmcheck: all temp-install + $(pg_isolation_regress_check) intra-grant-inplace-db + # Non-default tests. It only makes sense to run these if set up to use # prepared transactions, via TEMP_CONFIG for the check case, or via the # postgresql.conf for the installcheck case. diff --git a/src/test/isolation/expected/intra-grant-inplace-db.out b/src/test/isolation/expected/intra-grant-inplace-db.out index a91402c..c019952 100644 --- a/src/test/isolation/expected/intra-grant-inplace-db.out +++ b/src/test/isolation/expected/intra-grant-inplace-db.out @@ -1,6 +1,6 @@ Parsed test spec with 3 sessions -starting permutation: snap3 b1 grant1 vac2 snap3 c1 cmp3 +starting permutation: snap3 b1 grant1 wait vac2 snap3 c1 cmp3 step snap3: INSERT INTO frozen_witness SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog; @@ -9,6 +9,14 @@ step b1: BEGIN; step grant1: GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee; +step wait: + DO $$ + BEGIN + WHILE NOT EXISTS(SELECT 1 FROM pg_locks WHERE NOT granted AND transactionid IS NOT NULL) LOOP + NULL; + END LOOP; + END$$; + step vac2: VACUUM (FREEZE); step snap3: INSERT INTO frozen_witness diff --git a/src/test/isolation/specs/intra-grant-inplace-db.spec b/src/test/isolation/specs/intra-grant-inplace-db.spec index 9de40ec..7a9cae4 100644 --- a/src/test/isolation/specs/intra-grant-inplace-db.spec +++ b/src/test/isolation/specs/intra-grant-inplace-db.spec @@ -40,6 +40,19 @@ step cmp3 { WHERE datname = current_catalog AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness); } +# Busy-wait until some autovacuum workers's vac_update_datfrozenxid() blocks +# in heap_inplace_wait(), waiting for the xid of s1 to end. A vac2 at that +# point will block trying to acquire a cleanup lock on the page autovacuum has +# pinned. That's because systable_inplace_update_begin() acquires a pin that +# we hold until systable_inplace_update_finish(). +step wait { + DO $$ + BEGIN + WHILE NOT EXISTS(SELECT 1 FROM pg_locks WHERE NOT granted AND transactionid IS NOT NULL) LOOP + NULL; + END LOOP; + END$$; +} -permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3 +permutation snap3 b1 grant1 wait vac2(c1) snap3 c1 cmp3