On Thu, Jun 13, 2024 at 05:35:49PM -0700, Noah Misch wrote:
> On Mon, Jun 10, 2024 at 07:45:25PM -0700, Noah Misch wrote:
> > On Thu, Jun 06, 2024 at 09:48:51AM -0400, Robert Haas wrote:
> > > It's not this patch set's fault, but I'm not very pleased to see that
> > > the injection point wait events have been shoehorned into the
> > > "Extension" category
> > 
> > I've replied on that branch of the thread.
> 
> I think the attached covers all comments to date.  I gave everything v3, but
> most patches have just a no-conflict rebase vs. v2.  The exceptions are
> inplace031-inj-wait-event (implements the holding from that branch of the
> thread) and inplace050-tests-inj (updated to cooperate with inplace031).  Much
> of inplace031-inj-wait-event is essentially s/Extension/Custom/ for the
> infrastructure common to the two custom wait event types.

Starting 2024-06-27, I'd like to push
inplace080-catcache-detoast-inplace-stale and earlier patches, self-certifying
them if needed.  Then I'll submit the last three to the commitfest.  Does
anyone want me to delay that step?

Two more test-related changes compared to v3:

- In inplace010-tests, add to 027_stream_regress.pl a test that catalog
  contents match between primary and standby.  If one of these patches broke
  replay of inplace updates, this would help catch it.

- In inplace031-inj-wait-event, make sysviews.sql indifferent to whether
  InjectionPoint wait events exist.  installcheck need this if other activity
  created such an event since the last postmaster restart.
Author:     Noah Misch <n...@leadboat.com>
Commit:     Noah Misch <n...@leadboat.com>

    Improve test coverage for changes to inplace-updated catalogs.
    
    This covers both regular and inplace changes, since bugs arise at their
    intersection.  Where marked, these witness extant bugs.  Back-patch to
    v12 (all supported versions).
    
    Reviewed by FIXME.
    
    Discussion: https://postgr.es/m/20240512232923.aa.nmi...@google.com

diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl 
b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 59ea538..956e290 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -68,6 +68,34 @@ $node->pgbench(
                  "CREATE TYPE pg_temp.e AS ENUM ($labels); DROP TYPE 
pg_temp.e;"
        });
 
+# Test inplace updates from VACUUM concurrent with heap_update from GRANT.
+# The PROC_IN_VACUUM environment can't finish MVCC table scans consistently,
+# so this fails rarely.  To reproduce consistently, add a sleep after
+# GetCatalogSnapshot(non-catalog-rel).
+Test::More->builder->todo_start('PROC_IN_VACUUM scan breakage');
+$node->safe_psql('postgres', 'CREATE TABLE ddl_target ()');
+$node->pgbench(
+       '--no-vacuum --client=5 --protocol=prepared --transactions=50',
+       0,
+       [qr{processed: 250/250}],
+       [qr{^$}],
+       'concurrent GRANT/VACUUM',
+       {
+               '001_pgbench_grant@9' => q(
+                       DO $$
+                       BEGIN
+                               PERFORM pg_advisory_xact_lock(42);
+                               FOR i IN 1 .. 10 LOOP
+                                       GRANT SELECT ON ddl_target TO PUBLIC;
+                                       REVOKE SELECT ON ddl_target FROM PUBLIC;
+                               END LOOP;
+                       END
+                       $$;
+),
+               '001_pgbench_vacuum_ddl_target@1' => "VACUUM ddl_target;",
+       });
+Test::More->builder->todo_end;
+
 # Trigger various connection errors
 $node->pgbench(
        'no-such-database',
diff --git a/src/test/isolation/expected/eval-plan-qual.out 
b/src/test/isolation/expected/eval-plan-qual.out
index 0237271..032d420 100644
--- a/src/test/isolation/expected/eval-plan-qual.out
+++ b/src/test/isolation/expected/eval-plan-qual.out
@@ -1337,3 +1337,29 @@ a|b|c|   d
 2|2|2|1004
 (2 rows)
 
+
+starting permutation: sys1 sysupd2 c1 c2
+step sys1: 
+       UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+
+step sysupd2: 
+       UPDATE pg_class SET reltuples = reltuples * 2
+       WHERE oid = 'accounts'::regclass;
+ <waiting ...>
+step c1: COMMIT;
+step sysupd2: <... completed>
+step c2: COMMIT;
+
+starting permutation: sys1 sysmerge2 c1 c2
+step sys1: 
+       UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+
+step sysmerge2: 
+       MERGE INTO pg_class
+       USING (SELECT 'accounts'::regclass AS o) j
+       ON o = oid
+       WHEN MATCHED THEN UPDATE SET reltuples = reltuples * 2;
+ <waiting ...>
+step c1: COMMIT;
+step sysmerge2: <... completed>
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/inplace-inval.out 
b/src/test/isolation/expected/inplace-inval.out
new file mode 100644
index 0000000..67b34ad
--- /dev/null
+++ b/src/test/isolation/expected/inplace-inval.out
@@ -0,0 +1,32 @@
+Parsed test spec with 3 sessions
+
+starting permutation: cachefill3 cir1 cic2 ddl3 read1
+step cachefill3: TABLE newly_indexed;
+c
+-
+(0 rows)
+
+step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK;
+step cic2: CREATE INDEX i2 ON newly_indexed (c);
+step ddl3: ALTER TABLE newly_indexed ADD extra int;
+step read1: 
+       SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: cir1 cic2 ddl3 read1
+step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK;
+step cic2: CREATE INDEX i2 ON newly_indexed (c);
+step ddl3: ALTER TABLE newly_indexed ADD extra int;
+step read1: 
+       SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+
+relhasindex
+-----------
+t          
+(1 row)
+
diff --git a/src/test/isolation/expected/intra-grant-inplace-db.out 
b/src/test/isolation/expected/intra-grant-inplace-db.out
new file mode 100644
index 0000000..432ece5
--- /dev/null
+++ b/src/test/isolation/expected/intra-grant-inplace-db.out
@@ -0,0 +1,28 @@
+Parsed test spec with 3 sessions
+
+starting permutation: snap3 b1 grant1 vac2 snap3 c1 cmp3
+step snap3: 
+       INSERT INTO frozen_witness
+       SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+
+step b1: BEGIN;
+step grant1: 
+       GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
+
+step vac2: VACUUM (FREEZE);
+step snap3: 
+       INSERT INTO frozen_witness
+       SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+
+step c1: COMMIT;
+step cmp3: 
+       SELECT 'datfrozenxid retreated'
+       FROM pg_database
+       WHERE datname = current_catalog
+               AND age(datfrozenxid) > (SELECT min(age(x)) FROM 
frozen_witness);
+
+?column?              
+----------------------
+datfrozenxid retreated
+(1 row)
+
diff --git a/src/test/isolation/expected/intra-grant-inplace.out 
b/src/test/isolation/expected/intra-grant-inplace.out
new file mode 100644
index 0000000..cc1e47a
--- /dev/null
+++ b/src/test/isolation/expected/intra-grant-inplace.out
@@ -0,0 +1,225 @@
+Parsed test spec with 5 sessions
+
+starting permutation: b1 grant1 read2 addk2 c1 read2
+step b1: BEGIN;
+step grant1: 
+       GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+
+step read2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c1: COMMIT;
+step read2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: keyshr5 addk2
+step keyshr5: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+
+starting permutation: keyshr5 b3 sfnku3 addk2 r3
+step keyshr5: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfnku3: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step r3: ROLLBACK;
+
+starting permutation: b2 sfnku2 addk2 c2
+step b2: BEGIN;
+step sfnku2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+
+starting permutation: keyshr5 b2 sfnku2 addk2 c2
+step keyshr5: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b2: BEGIN;
+step sfnku2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+
+starting permutation: b3 sfu3 b1 grant1 read2 addk2 r3 c1 read2
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b1: BEGIN;
+step grant1: 
+       GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+ <waiting ...>
+step read2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step r3: ROLLBACK;
+step grant1: <... completed>
+step c1: COMMIT;
+step read2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: b2 sfnku2 b1 grant1 addk2 c2 c1 read2
+step b2: BEGIN;
+step sfnku2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b1: BEGIN;
+step grant1: 
+       GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+ <waiting ...>
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+step grant1: <... completed>
+step c1: COMMIT;
+step read2: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: b1 grant1 b3 sfu3 revoke4 c1 r3
+step b1: BEGIN;
+step grant1: 
+       GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+ <waiting ...>
+step revoke4: 
+       DO $$
+       BEGIN
+               REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+       EXCEPTION WHEN others THEN
+               RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 
'REDACTED');
+       END
+       $$;
+ <waiting ...>
+step c1: COMMIT;
+step sfu3: <... completed>
+relhasindex
+-----------
+f          
+(1 row)
+
+s4: WARNING:  got: tuple concurrently updated
+step revoke4: <... completed>
+step r3: ROLLBACK;
+
+starting permutation: b1 drop1 b3 sfu3 revoke4 c1 r3
+step b1: BEGIN;
+step drop1: 
+       DROP TABLE intra_grant_inplace;
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+ <waiting ...>
+step revoke4: 
+       DO $$
+       BEGIN
+               REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+       EXCEPTION WHEN others THEN
+               RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 
'REDACTED');
+       END
+       $$;
+ <waiting ...>
+step c1: COMMIT;
+step sfu3: <... completed>
+relhasindex
+-----------
+(0 rows)
+
+s4: WARNING:  got: tuple concurrently deleted
+step revoke4: <... completed>
+step r3: ROLLBACK;
diff --git a/src/test/isolation/isolation_schedule 
b/src/test/isolation/isolation_schedule
index 0342eb3..6da98cf 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -37,6 +37,9 @@ test: fk-snapshot
 test: subxid-overflow
 test: eval-plan-qual
 test: eval-plan-qual-trigger
+test: inplace-inval
+test: intra-grant-inplace
+test: intra-grant-inplace-db
 test: lock-update-delete
 test: lock-update-traversal
 test: inherit-temp
diff --git a/src/test/isolation/specs/eval-plan-qual.spec 
b/src/test/isolation/specs/eval-plan-qual.spec
index edd6d19..3a74406 100644
--- a/src/test/isolation/specs/eval-plan-qual.spec
+++ b/src/test/isolation/specs/eval-plan-qual.spec
@@ -194,6 +194,12 @@ step simplepartupdate_noroute {
        update parttbl set b = 2 where c = 1 returning *;
 }
 
+# test system class updates
+
+step sys1      {
+       UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+}
+
 
 session s2
 setup          { BEGIN ISOLATION LEVEL READ COMMITTED; }
@@ -282,6 +288,18 @@ step wnested2 {
     );
 }
 
+step sysupd2   {
+       UPDATE pg_class SET reltuples = reltuples * 2
+       WHERE oid = 'accounts'::regclass;
+}
+
+step sysmerge2 {
+       MERGE INTO pg_class
+       USING (SELECT 'accounts'::regclass AS o) j
+       ON o = oid
+       WHEN MATCHED THEN UPDATE SET reltuples = reltuples * 2;
+}
+
 step c2        { COMMIT; }
 step r2        { ROLLBACK; }
 
@@ -380,3 +398,6 @@ permutation simplepartupdate complexpartupdate c1 c2 
read_part
 permutation simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2 
read_part
 permutation simplepartupdate_noroute complexpartupdate_route c1 c2 read_part
 permutation simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2 
read_part
+
+permutation sys1 sysupd2 c1 c2
+permutation sys1 sysmerge2 c1 c2
diff --git a/src/test/isolation/specs/inplace-inval.spec 
b/src/test/isolation/specs/inplace-inval.spec
new file mode 100644
index 0000000..d8e1c98
--- /dev/null
+++ b/src/test/isolation/specs/inplace-inval.spec
@@ -0,0 +1,38 @@
+# If a heap_update() caller retrieves its oldtup from a cache, it's possible
+# for that cache entry to predate an inplace update, causing loss of that
+# inplace update.  This arises because the transaction may abort before
+# sending the inplace invalidation message to the shared queue.
+
+setup
+{
+       CREATE TABLE newly_indexed (c int);
+}
+
+teardown
+{
+       DROP TABLE newly_indexed;
+}
+
+session s1
+step cir1      { BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK; }
+step read1     {
+       SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+}
+
+session s2
+step cic2      { CREATE INDEX i2 ON newly_indexed (c); }
+
+session s3
+step cachefill3        { TABLE newly_indexed; }
+step ddl3              { ALTER TABLE newly_indexed ADD extra int; }
+
+
+permutation
+       cachefill3      # populates the pg_class row in the catcache
+       cir1    # sets relhasindex=true; rollback discards cache inval
+       cic2    # sees relhasindex=true, skips changing it (so no inval)
+       ddl3    # cached row as the oldtup of an update, losing relhasindex
+       read1   # observe damage XXX is an extant bug
+
+# without cachefill3, no bug
+permutation cir1 cic2 ddl3 read1
diff --git a/src/test/isolation/specs/intra-grant-inplace-db.spec 
b/src/test/isolation/specs/intra-grant-inplace-db.spec
new file mode 100644
index 0000000..bbecd5d
--- /dev/null
+++ b/src/test/isolation/specs/intra-grant-inplace-db.spec
@@ -0,0 +1,46 @@
+# GRANT's lock is the catalog tuple xmax.  GRANT doesn't acquire a heavyweight
+# lock on the object undergoing an ACL change.  In-place updates, namely
+# datfrozenxid, need special code to cope.
+
+setup
+{
+       CREATE ROLE regress_temp_grantee;
+}
+
+teardown
+{
+       REVOKE ALL ON DATABASE isolation_regression FROM regress_temp_grantee;
+       DROP ROLE regress_temp_grantee;
+}
+
+# heap_update(pg_database)
+session s1
+step b1        { BEGIN; }
+step grant1    {
+       GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
+}
+step c1        { COMMIT; }
+
+# inplace update
+session s2
+step vac2      { VACUUM (FREEZE); }
+
+# observe datfrozenxid
+session s3
+setup  {
+       CREATE TEMP TABLE frozen_witness (x xid);
+}
+step snap3     {
+       INSERT INTO frozen_witness
+       SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+}
+step cmp3      {
+       SELECT 'datfrozenxid retreated'
+       FROM pg_database
+       WHERE datname = current_catalog
+               AND age(datfrozenxid) > (SELECT min(age(x)) FROM 
frozen_witness);
+}
+
+
+# XXX extant bug
+permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3
diff --git a/src/test/isolation/specs/intra-grant-inplace.spec 
b/src/test/isolation/specs/intra-grant-inplace.spec
new file mode 100644
index 0000000..3cd696b
--- /dev/null
+++ b/src/test/isolation/specs/intra-grant-inplace.spec
@@ -0,0 +1,153 @@
+# GRANT's lock is the catalog tuple xmax.  GRANT doesn't acquire a heavyweight
+# lock on the object undergoing an ACL change.  Inplace updates, such as
+# relhasindex=true, need special code to cope.
+
+setup
+{
+       CREATE TABLE intra_grant_inplace (c int);
+}
+
+teardown
+{
+       DROP TABLE IF EXISTS intra_grant_inplace;
+}
+
+# heap_update()
+session s1
+step b1        { BEGIN; }
+step grant1    {
+       GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+}
+step drop1     {
+       DROP TABLE intra_grant_inplace;
+}
+step c1        { COMMIT; }
+
+# inplace update
+session s2
+step read2     {
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass;
+}
+step b2                { BEGIN; }
+step addk2     { ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); }
+step sfnku2    {
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+}
+step c2                { COMMIT; }
+
+# rowmarks
+session s3
+step b3                { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step sfnku3    {
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+}
+step sfu3      {
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+}
+step r3        { ROLLBACK; }
+
+# Additional heap_update()
+session s4
+# swallow error message to keep any OID value out of expected output
+step revoke4   {
+       DO $$
+       BEGIN
+               REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+       EXCEPTION WHEN others THEN
+               RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 
'REDACTED');
+       END
+       $$;
+}
+
+# Additional rowmarks
+session s5
+setup  { BEGIN; }
+step keyshr5   {
+       SELECT relhasindex FROM pg_class
+       WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+}
+teardown       { ROLLBACK; }
+
+
+# XXX extant bugs: permutation comments refer to planned post-bugfix behavior
+
+permutation
+       b1
+       grant1
+       read2
+       addk2(c1)       # inplace waits
+       c1
+       read2
+
+# inplace thru KEY SHARE
+permutation
+       keyshr5
+       addk2
+
+# inplace wait NO KEY UPDATE w/ KEY SHARE
+permutation
+       keyshr5
+       b3
+       sfnku3
+       addk2(r3)
+       r3
+
+# same-xact rowmark
+permutation
+       b2
+       sfnku2
+       addk2
+       c2
+
+# same-xact rowmark in multixact
+permutation
+       keyshr5
+       b2
+       sfnku2
+       addk2
+       c2
+
+permutation
+       b3
+       sfu3
+       b1
+       grant1(r3)      # acquire LockTuple(), await sfu3 xmax
+       read2
+       addk2(c1)       # block in LockTuple() behind grant1
+       r3                      # unblock grant1; addk2 now awaits grant1 xmax
+       c1
+       read2
+
+permutation
+       b2
+       sfnku2
+       b1
+       grant1(c2)              # acquire LockTuple(), await sfnku2 xmax
+       addk2                   # block in LockTuple() behind grant1 = deadlock
+       c2
+       c1
+       read2
+
+# SearchSysCacheLocked1() calling LockRelease()
+permutation
+       b1
+       grant1
+       b3
+       sfu3(c1)        # acquire LockTuple(), await grant1 xmax
+       revoke4(sfu3)   # block in LockTuple() behind sfu3
+       c1
+       r3                      # revoke4 unlocks old tuple and finds new
+
+# SearchSysCacheLocked1() finding a tuple, then no tuple
+permutation
+       b1
+       drop1
+       b3
+       sfu3(c1)                # acquire LockTuple(), await drop1 xmax
+       revoke4(sfu3)   # block in LockTuple() behind sfu3
+       c1                              # sfu3 locks none; revoke4 unlocks old 
and finds none
+       r3
diff --git a/src/test/recovery/t/027_stream_regress.pl 
b/src/test/recovery/t/027_stream_regress.pl
index ae8dea0..d1ae32d 100644
--- a/src/test/recovery/t/027_stream_regress.pl
+++ b/src/test/recovery/t/027_stream_regress.pl
@@ -120,6 +120,40 @@ command_ok(
        [ 'diff', $outputdir . '/primary.dump', $outputdir . '/standby.dump' ],
        'compare primary and standby dumps');
 
+# Likewise for the catalogs of the regression database, after disabling
+# autovacuum to make fields like relpages stop changing.
+$node_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$node_primary->restart;
+$node_primary->wait_for_replay_catchup($node_standby_1);
+command_ok(
+       [
+               'pg_dump',
+               ('--schema', 'pg_catalog'),
+               ('-f', $outputdir . '/catalogs_primary.dump'),
+               '--no-sync',
+               ('-p', $node_primary->port),
+               '--no-unlogged-table-data',
+               'regression'
+       ],
+       'dump catalogs of primary server');
+command_ok(
+       [
+               'pg_dump',
+               ('--schema', 'pg_catalog'),
+               ('-f', $outputdir . '/catalogs_standby.dump'),
+               '--no-sync',
+               ('-p', $node_standby_1->port),
+               'regression'
+       ],
+       'dump catalogs of standby server');
+command_ok(
+       [
+               'diff',
+               $outputdir . '/catalogs_primary.dump',
+               $outputdir . '/catalogs_standby.dump'
+       ],
+       'compare primary and standby catalog dumps');
+
 # Check some data from pg_stat_statements.
 $node_primary->safe_psql('postgres', 'CREATE EXTENSION pg_stat_statements');
 # This gathers data based on the first characters for some common query types,
diff --git a/src/test/regress/expected/database.out 
b/src/test/regress/expected/database.out
new file mode 100644
index 0000000..30c0865
--- /dev/null
+++ b/src/test/regress/expected/database.out
@@ -0,0 +1,14 @@
+CREATE DATABASE regression_tbd ENCODING utf8 LOCALE "C" TEMPLATE template0;
+ALTER DATABASE regression_tbd RENAME TO regression_utf8;
+ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace;
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123;
+-- Test PgDatabaseToastTable.  Doing this organically and portably would take
+-- a huge relacl, which would be slow.
+BEGIN;
+UPDATE pg_database SET datcollversion = repeat('a', 6e6::int)
+WHERE datname = 'regression_utf8';
+-- load catcache entry, if nothing else does
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ROLLBACK;
+DROP DATABASE regression_utf8;
diff --git a/src/test/regress/expected/merge.out 
b/src/test/regress/expected/merge.out
index eddc1f4..3d33259 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -2691,6 +2691,30 @@ drop cascades to table measurement_y2007m01
 DROP FUNCTION measurement_insert_trigger();
 -- prepare
 RESET SESSION AUTHORIZATION;
+-- try a system catalog
+MERGE INTO pg_class c
+USING (SELECT 'pg_depend'::regclass AS oid) AS j
+ON j.oid = c.oid
+WHEN MATCHED THEN
+       UPDATE SET reltuples = reltuples + 1
+RETURNING j.oid;
+    oid    
+-----------
+ pg_depend
+(1 row)
+
+CREATE VIEW classv AS SELECT * FROM pg_class;
+MERGE INTO classv c
+USING pg_namespace n
+ON n.oid = c.relnamespace
+WHEN MATCHED AND c.oid = 'pg_depend'::regclass THEN
+       UPDATE SET reltuples = reltuples - 1
+RETURNING c.oid;
+ oid  
+------
+ 2608
+(1 row)
+
 DROP TABLE target, target2;
 DROP TABLE source, source2;
 DROP FUNCTION merge_trigfunc();
diff --git a/src/test/regress/parallel_schedule 
b/src/test/regress/parallel_schedule
index 969ced9..2429ec2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -28,7 +28,7 @@ test: strings md5 numerology point lseg line box path polygon 
circle date time t
 # geometry depends on point, lseg, line, box, path, polygon, circle
 # horology depends on date, time, timetz, timestamp, timestamptz, interval
 # ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity 
comments expressions unicode xid mvcc
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity 
comments expressions unicode xid mvcc database
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/sql/database.sql 
b/src/test/regress/sql/database.sql
new file mode 100644
index 0000000..6c61f2e
--- /dev/null
+++ b/src/test/regress/sql/database.sql
@@ -0,0 +1,16 @@
+CREATE DATABASE regression_tbd ENCODING utf8 LOCALE "C" TEMPLATE template0;
+ALTER DATABASE regression_tbd RENAME TO regression_utf8;
+ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace;
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123;
+
+-- Test PgDatabaseToastTable.  Doing this organically and portably would take
+-- a huge relacl, which would be slow.
+BEGIN;
+UPDATE pg_database SET datcollversion = repeat('a', 6e6::int)
+WHERE datname = 'regression_utf8';
+-- load catcache entry, if nothing else does
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ROLLBACK;
+
+DROP DATABASE regression_utf8;
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
index 3d5d854..92163ec 100644
--- a/src/test/regress/sql/merge.sql
+++ b/src/test/regress/sql/merge.sql
@@ -1713,6 +1713,23 @@ DROP FUNCTION measurement_insert_trigger();
 -- prepare
 
 RESET SESSION AUTHORIZATION;
+
+-- try a system catalog
+MERGE INTO pg_class c
+USING (SELECT 'pg_depend'::regclass AS oid) AS j
+ON j.oid = c.oid
+WHEN MATCHED THEN
+       UPDATE SET reltuples = reltuples + 1
+RETURNING j.oid;
+
+CREATE VIEW classv AS SELECT * FROM pg_class;
+MERGE INTO classv c
+USING pg_namespace n
+ON n.oid = c.relnamespace
+WHEN MATCHED AND c.oid = 'pg_depend'::regclass THEN
+       UPDATE SET reltuples = reltuples - 1
+RETURNING c.oid;
+
 DROP TABLE target, target2;
 DROP TABLE source, source2;
 DROP FUNCTION merge_trigfunc();
Author:     Noah Misch <n...@leadboat.com>
Commit:     Noah Misch <n...@leadboat.com>

    Add wait event type "InjectionPoint", a custom type like "Extension".
    
    Both injection points and customization of type "Extension" are new in
    v17, so this just changes a detail of an unreleased feature.
    
    Reported by Robert Haas.  Reviewed by Michael Paquier.
    
    Discussion: 
https://postgr.es/m/ca+tgmobfmu5pdxp36d5iawxv5wke_vudltp_1qyh+h5jmmt...@mail.gmail.com

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 053da8d..8233f98 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1064,6 +1064,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   
11:34   0:00 postgres: ser
       </entry>
      </row>
      <row>
+      <entry><literal>InjectionPoint</literal></entry>
+      <entry>The server process is waiting for an injection point to reach an
+       outcome defined in a test.  See
+       <xref linkend="xfunc-addin-injection-points"/> for more details.  This
+       type has no predefined wait points.
+      </entry>
+     </row>
+     <row>
       <entry><literal>IO</literal></entry>
       <entry>The server process is waiting for an I/O operation to complete.
        <literal>wait_event</literal> will identify the specific wait point;
@@ -1139,8 +1147,8 @@ description | Waiting for a newly initialized WAL file to 
reach durable storage
 
    <note>
     <para>
-     Extensions can add <literal>Extension</literal> and
-     <literal>LWLock</literal> events
+     Extensions can add <literal>Extension</literal>,
+     <literal>InjectionPoint</literal>. and <literal>LWLock</literal> events
      to the lists shown in <xref linkend="wait-event-extension-table"/> and
      <xref linkend="wait-event-lwlock-table"/>. In some cases, the name
      of an <literal>LWLock</literal> assigned by an extension will not be
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index a7c1704..66c1c30 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3643,7 +3643,11 @@ extern void InjectionPointAttach(const char *name,
 static void
 custom_injection_callback(const char *name, const void *private_data)
 {
+    uint32 wait_event_info = WaitEventInjectionPointNew(name);
+
+    pgstat_report_wait_start(wait_event_info);
     elog(NOTICE, "%s: executed custom callback", name);
+    pgstat_report_wait_end();
 }
 </programlisting>
      This callback prints a message to server error log with severity
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 521ed54..2100150 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -149,7 +149,7 @@ CalculateShmemSize(int *num_semaphores)
        size = add_size(size, SyncScanShmemSize());
        size = add_size(size, AsyncShmemSize());
        size = add_size(size, StatsShmemSize());
-       size = add_size(size, WaitEventExtensionShmemSize());
+       size = add_size(size, WaitEventCustomShmemSize());
        size = add_size(size, InjectionPointShmemSize());
        size = add_size(size, SlotSyncShmemSize());
 #ifdef EXEC_BACKEND
@@ -355,7 +355,7 @@ CreateOrAttachShmemStructs(void)
        SyncScanShmemInit();
        AsyncShmemInit();
        StatsShmemInit();
-       WaitEventExtensionShmemInit();
+       WaitEventCustomShmemInit();
        InjectionPointShmemInit();
 }
 
diff --git a/src/backend/utils/activity/generate-wait_event_types.pl 
b/src/backend/utils/activity/generate-wait_event_types.pl
index 42f36f4..6a9c0a5 100644
--- a/src/backend/utils/activity/generate-wait_event_types.pl
+++ b/src/backend/utils/activity/generate-wait_event_types.pl
@@ -181,9 +181,10 @@ if ($gen_code)
        foreach my $waitclass (sort { uc($a) cmp uc($b) } keys %hashwe)
        {
                # Don't generate the pgstat_wait_event.c and wait_event_types.h 
files
-               # for Extension, LWLock and Lock, these are handled 
independently.
+               # for types handled independently.
                next
                  if ( $waitclass eq 'WaitEventExtension'
+                       || $waitclass eq 'WaitEventInjectionPoint'
                        || $waitclass eq 'WaitEventLWLock'
                        || $waitclass eq 'WaitEventLock');
 
diff --git a/src/backend/utils/activity/wait_event.c 
b/src/backend/utils/activity/wait_event.c
index 084a9df..bbf5948 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -47,68 +47,69 @@ uint32         *my_wait_event_info = 
&local_my_wait_event_info;
  * Hash tables for storing custom wait event ids and their names in
  * shared memory.
  *
- * WaitEventExtensionHashById is used to find the name from an event id.
- * Any backend can search it to find custom wait events.
+ * WaitEventCustomHashByInfo is used to find the name from wait event
+ * information.  Any backend can search it to find custom wait events.
  *
- * WaitEventExtensionHashByName is used to find the event ID from a name.
- * It is used to ensure that no duplicated entries are registered.
+ * WaitEventCustomHashByName is used to find the wait event information from a
+ * name.  It is used to ensure that no duplicated entries are registered.
+ *
+ * For simplicity, we use the same ID counter across types of custom events.
+ * We could end that anytime the need arises.
  *
  * The size of the hash table is based on the assumption that
- * WAIT_EVENT_EXTENSION_HASH_INIT_SIZE is enough for most cases, and it seems
+ * WAIT_EVENT_CUSTOM_HASH_INIT_SIZE is enough for most cases, and it seems
  * unlikely that the number of entries will reach
- * WAIT_EVENT_EXTENSION_HASH_MAX_SIZE.
+ * WAIT_EVENT_CUSTOM_HASH_MAX_SIZE.
  */
-static HTAB *WaitEventExtensionHashById;       /* find names from IDs */
-static HTAB *WaitEventExtensionHashByName;     /* find IDs from names */
+static HTAB *WaitEventCustomHashByInfo; /* find names from infos */
+static HTAB *WaitEventCustomHashByName; /* find infos from names */
 
-#define WAIT_EVENT_EXTENSION_HASH_INIT_SIZE    16
-#define WAIT_EVENT_EXTENSION_HASH_MAX_SIZE     128
+#define WAIT_EVENT_CUSTOM_HASH_INIT_SIZE       16
+#define WAIT_EVENT_CUSTOM_HASH_MAX_SIZE        128
 
 /* hash table entries */
-typedef struct WaitEventExtensionEntryById
+typedef struct WaitEventCustomEntryByInfo
 {
-       uint16          event_id;               /* hash key */
+       uint32          wait_event_info;        /* hash key */
        char            wait_event_name[NAMEDATALEN];   /* custom wait event 
name */
-} WaitEventExtensionEntryById;
+} WaitEventCustomEntryByInfo;
 
-typedef struct WaitEventExtensionEntryByName
+typedef struct WaitEventCustomEntryByName
 {
        char            wait_event_name[NAMEDATALEN];   /* hash key */
-       uint16          event_id;               /* wait event ID */
-} WaitEventExtensionEntryByName;
+       uint32          wait_event_info;
+} WaitEventCustomEntryByName;
 
 
-/* dynamic allocation counter for custom wait events in extensions */
-typedef struct WaitEventExtensionCounterData
+/* dynamic allocation counter for custom wait events */
+typedef struct WaitEventCustomCounterData
 {
        int                     nextId;                 /* next ID to assign */
        slock_t         mutex;                  /* protects the counter */
-} WaitEventExtensionCounterData;
+} WaitEventCustomCounterData;
 
 /* pointer to the shared memory */
-static WaitEventExtensionCounterData *WaitEventExtensionCounter;
+static WaitEventCustomCounterData *WaitEventCustomCounter;
 
-/* first event ID of custom wait events for extensions */
-#define WAIT_EVENT_EXTENSION_INITIAL_ID        1
+/* first event ID of custom wait events */
+#define WAIT_EVENT_CUSTOM_INITIAL_ID   1
 
-/* wait event info for extensions */
-#define WAIT_EVENT_EXTENSION_INFO(eventId)     (PG_WAIT_EXTENSION | eventId)
-
-static const char *GetWaitEventExtensionIdentifier(uint16 eventId);
+static uint32 WaitEventCustomNew(uint32 classId, const char *wait_event_name);
+static const char *GetWaitEventCustomIdentifier(uint32 wait_event_info);
 
 /*
  *  Return the space for dynamic shared hash tables and dynamic allocation 
counter.
  */
 Size
-WaitEventExtensionShmemSize(void)
+WaitEventCustomShmemSize(void)
 {
        Size            sz;
 
-       sz = MAXALIGN(sizeof(WaitEventExtensionCounterData));
-       sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
-                                                                               
 sizeof(WaitEventExtensionEntryById)));
-       sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
-                                                                               
 sizeof(WaitEventExtensionEntryByName)));
+       sz = MAXALIGN(sizeof(WaitEventCustomCounterData));
+       sz = add_size(sz, hash_estimate_size(WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
+                                                                               
 sizeof(WaitEventCustomEntryByInfo)));
+       sz = add_size(sz, hash_estimate_size(WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
+                                                                               
 sizeof(WaitEventCustomEntryByName)));
        return sz;
 }
 
@@ -116,39 +117,41 @@ WaitEventExtensionShmemSize(void)
  * Allocate shmem space for dynamic shared hash and dynamic allocation counter.
  */
 void
-WaitEventExtensionShmemInit(void)
+WaitEventCustomShmemInit(void)
 {
        bool            found;
        HASHCTL         info;
 
-       WaitEventExtensionCounter = (WaitEventExtensionCounterData *)
-               ShmemInitStruct("WaitEventExtensionCounterData",
-                                               
sizeof(WaitEventExtensionCounterData), &found);
+       WaitEventCustomCounter = (WaitEventCustomCounterData *)
+               ShmemInitStruct("WaitEventCustomCounterData",
+                                               
sizeof(WaitEventCustomCounterData), &found);
 
        if (!found)
        {
                /* initialize the allocation counter and its spinlock. */
-               WaitEventExtensionCounter->nextId = 
WAIT_EVENT_EXTENSION_INITIAL_ID;
-               SpinLockInit(&WaitEventExtensionCounter->mutex);
+               WaitEventCustomCounter->nextId = WAIT_EVENT_CUSTOM_INITIAL_ID;
+               SpinLockInit(&WaitEventCustomCounter->mutex);
        }
 
        /* initialize or attach the hash tables to store custom wait events */
-       info.keysize = sizeof(uint16);
-       info.entrysize = sizeof(WaitEventExtensionEntryById);
-       WaitEventExtensionHashById = ShmemInitHash("WaitEventExtension hash by 
id",
-                                                                               
           WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
-                                                                               
           WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
-                                                                               
           &info,
-                                                                               
           HASH_ELEM | HASH_BLOBS);
+       info.keysize = sizeof(uint32);
+       info.entrysize = sizeof(WaitEventCustomEntryByInfo);
+       WaitEventCustomHashByInfo =
+               ShmemInitHash("WaitEventCustom hash by wait event information",
+                                         WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
+                                         WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
+                                         &info,
+                                         HASH_ELEM | HASH_BLOBS);
 
        /* key is a NULL-terminated string */
        info.keysize = sizeof(char[NAMEDATALEN]);
-       info.entrysize = sizeof(WaitEventExtensionEntryByName);
-       WaitEventExtensionHashByName = ShmemInitHash("WaitEventExtension hash 
by name",
-                                                                               
                 WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
-                                                                               
                 WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
-                                                                               
                 &info,
-                                                                               
                 HASH_ELEM | HASH_STRINGS);
+       info.entrysize = sizeof(WaitEventCustomEntryByName);
+       WaitEventCustomHashByName =
+               ShmemInitHash("WaitEventCustom hash by name",
+                                         WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
+                                         WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
+                                         &info,
+                                         HASH_ELEM | HASH_STRINGS);
 }
 
 /*
@@ -160,10 +163,23 @@ WaitEventExtensionShmemInit(void)
 uint32
 WaitEventExtensionNew(const char *wait_event_name)
 {
+       return WaitEventCustomNew(PG_WAIT_EXTENSION, wait_event_name);
+}
+
+uint32
+WaitEventInjectionPointNew(const char *wait_event_name)
+{
+       return WaitEventCustomNew(PG_WAIT_INJECTIONPOINT, wait_event_name);
+}
+
+static uint32
+WaitEventCustomNew(uint32 classId, const char *wait_event_name)
+{
        uint16          eventId;
        bool            found;
-       WaitEventExtensionEntryByName *entry_by_name;
-       WaitEventExtensionEntryById *entry_by_id;
+       WaitEventCustomEntryByName *entry_by_name;
+       WaitEventCustomEntryByInfo *entry_by_info;
+       uint32          wait_event_info;
 
        /* Check the limit of the length of the event name */
        if (strlen(wait_event_name) >= NAMEDATALEN)
@@ -175,13 +191,24 @@ WaitEventExtensionNew(const char *wait_event_name)
         * Check if the wait event info associated to the name is already 
defined,
         * and return it if so.
         */
-       LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
-       entry_by_name = (WaitEventExtensionEntryByName *)
-               hash_search(WaitEventExtensionHashByName, wait_event_name,
+       LWLockAcquire(WaitEventCustomLock, LW_SHARED);
+       entry_by_name = (WaitEventCustomEntryByName *)
+               hash_search(WaitEventCustomHashByName, wait_event_name,
                                        HASH_FIND, &found);
-       LWLockRelease(WaitEventExtensionLock);
+       LWLockRelease(WaitEventCustomLock);
        if (found)
-               return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
+       {
+               uint32          oldClassId;
+
+               oldClassId = entry_by_name->wait_event_info & 
WAIT_EVENT_CLASS_MASK;
+               if (oldClassId != classId)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("wait event \"%s\" already 
exists in type \"%s\"",
+                                                       wait_event_name,
+                                                       
pgstat_get_wait_event_type(entry_by_name->wait_event_info))));
+               return entry_by_name->wait_event_info;
+       }
 
        /*
         * Allocate and register a new wait event.  Recheck if the event name
@@ -189,113 +216,123 @@ WaitEventExtensionNew(const char *wait_event_name)
         * one with the same name since the LWLock acquired again here was
         * previously released.
         */
-       LWLockAcquire(WaitEventExtensionLock, LW_EXCLUSIVE);
-       entry_by_name = (WaitEventExtensionEntryByName *)
-               hash_search(WaitEventExtensionHashByName, wait_event_name,
+       LWLockAcquire(WaitEventCustomLock, LW_EXCLUSIVE);
+       entry_by_name = (WaitEventCustomEntryByName *)
+               hash_search(WaitEventCustomHashByName, wait_event_name,
                                        HASH_FIND, &found);
        if (found)
        {
-               LWLockRelease(WaitEventExtensionLock);
-               return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
+               uint32          oldClassId;
+
+               LWLockRelease(WaitEventCustomLock);
+               oldClassId = entry_by_name->wait_event_info & 
WAIT_EVENT_CLASS_MASK;
+               if (oldClassId != classId)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("wait event \"%s\" already 
exists in type \"%s\"",
+                                                       wait_event_name,
+                                                       
pgstat_get_wait_event_type(entry_by_name->wait_event_info))));
+               return entry_by_name->wait_event_info;
        }
 
        /* Allocate a new event Id */
-       SpinLockAcquire(&WaitEventExtensionCounter->mutex);
+       SpinLockAcquire(&WaitEventCustomCounter->mutex);
 
-       if (WaitEventExtensionCounter->nextId >= 
WAIT_EVENT_EXTENSION_HASH_MAX_SIZE)
+       if (WaitEventCustomCounter->nextId >= WAIT_EVENT_CUSTOM_HASH_MAX_SIZE)
        {
-               SpinLockRelease(&WaitEventExtensionCounter->mutex);
+               SpinLockRelease(&WaitEventCustomCounter->mutex);
                ereport(ERROR,
                                errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                               errmsg("too many wait events for extensions"));
+                               errmsg("too many custom wait events"));
        }
 
-       eventId = WaitEventExtensionCounter->nextId++;
+       eventId = WaitEventCustomCounter->nextId++;
 
-       SpinLockRelease(&WaitEventExtensionCounter->mutex);
+       SpinLockRelease(&WaitEventCustomCounter->mutex);
 
        /* Register the new wait event */
-       entry_by_id = (WaitEventExtensionEntryById *)
-               hash_search(WaitEventExtensionHashById, &eventId,
+       wait_event_info = classId | eventId;
+       entry_by_info = (WaitEventCustomEntryByInfo *)
+               hash_search(WaitEventCustomHashByInfo, &wait_event_info,
                                        HASH_ENTER, &found);
        Assert(!found);
-       strlcpy(entry_by_id->wait_event_name, wait_event_name,
-                       sizeof(entry_by_id->wait_event_name));
+       strlcpy(entry_by_info->wait_event_name, wait_event_name,
+                       sizeof(entry_by_info->wait_event_name));
 
-       entry_by_name = (WaitEventExtensionEntryByName *)
-               hash_search(WaitEventExtensionHashByName, wait_event_name,
+       entry_by_name = (WaitEventCustomEntryByName *)
+               hash_search(WaitEventCustomHashByName, wait_event_name,
                                        HASH_ENTER, &found);
        Assert(!found);
-       entry_by_name->event_id = eventId;
+       entry_by_name->wait_event_info = wait_event_info;
 
-       LWLockRelease(WaitEventExtensionLock);
+       LWLockRelease(WaitEventCustomLock);
 
-       return WAIT_EVENT_EXTENSION_INFO(eventId);
+       return wait_event_info;
 }
 
 /*
- * Return the name of an wait event ID for extension.
+ * Return the name of a custom wait event information.
  */
 static const char *
-GetWaitEventExtensionIdentifier(uint16 eventId)
+GetWaitEventCustomIdentifier(uint32 wait_event_info)
 {
        bool            found;
-       WaitEventExtensionEntryById *entry;
+       WaitEventCustomEntryByInfo *entry;
 
        /* Built-in event? */
-       if (eventId < WAIT_EVENT_EXTENSION_INITIAL_ID)
+       if (wait_event_info == PG_WAIT_EXTENSION)
                return "Extension";
 
        /* It is a user-defined wait event, so lookup hash table. */
-       LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
-       entry = (WaitEventExtensionEntryById *)
-               hash_search(WaitEventExtensionHashById, &eventId,
+       LWLockAcquire(WaitEventCustomLock, LW_SHARED);
+       entry = (WaitEventCustomEntryByInfo *)
+               hash_search(WaitEventCustomHashByInfo, &wait_event_info,
                                        HASH_FIND, &found);
-       LWLockRelease(WaitEventExtensionLock);
+       LWLockRelease(WaitEventCustomLock);
 
        if (!entry)
-               elog(ERROR, "could not find custom wait event name for ID %u",
-                        eventId);
+               elog(ERROR,
+                        "could not find custom name for wait event information 
%u",
+                        wait_event_info);
 
        return entry->wait_event_name;
 }
 
 
 /*
- * Returns a list of currently defined custom wait event names for extensions.
- * The result is a palloc'd array, with the number of elements saved in
- * *nwaitevents.
+ * Returns a list of currently defined custom wait event names.  The result is
+ * a palloc'd array, with the number of elements saved in *nwaitevents.
  */
 char     **
-GetWaitEventExtensionNames(int *nwaitevents)
+GetWaitEventCustomNames(uint32 classId, int *nwaitevents)
 {
        char      **waiteventnames;
-       WaitEventExtensionEntryByName *hentry;
+       WaitEventCustomEntryByName *hentry;
        HASH_SEQ_STATUS hash_seq;
        int                     index;
        int                     els;
 
-       LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
+       LWLockAcquire(WaitEventCustomLock, LW_SHARED);
 
        /* Now we can safely count the number of entries */
-       els = hash_get_num_entries(WaitEventExtensionHashByName);
+       els = hash_get_num_entries(WaitEventCustomHashByName);
 
        /* Allocate enough space for all entries */
        waiteventnames = palloc(els * sizeof(char *));
 
        /* Now scan the hash table to copy the data */
-       hash_seq_init(&hash_seq, WaitEventExtensionHashByName);
+       hash_seq_init(&hash_seq, WaitEventCustomHashByName);
 
        index = 0;
-       while ((hentry = (WaitEventExtensionEntryByName *) 
hash_seq_search(&hash_seq)) != NULL)
+       while ((hentry = (WaitEventCustomEntryByName *) 
hash_seq_search(&hash_seq)) != NULL)
        {
+               if ((hentry->wait_event_info & WAIT_EVENT_CLASS_MASK) != 
classId)
+                       continue;
                waiteventnames[index] = pstrdup(hentry->wait_event_name);
                index++;
        }
 
-       LWLockRelease(WaitEventExtensionLock);
-
-       Assert(index == els);
+       LWLockRelease(WaitEventCustomLock);
 
        *nwaitevents = index;
        return waiteventnames;
@@ -374,6 +411,9 @@ pgstat_get_wait_event_type(uint32 wait_event_info)
                case PG_WAIT_IO:
                        event_type = "IO";
                        break;
+               case PG_WAIT_INJECTIONPOINT:
+                       event_type = "InjectionPoint";
+                       break;
                default:
                        event_type = "???";
                        break;
@@ -411,7 +451,8 @@ pgstat_get_wait_event(uint32 wait_event_info)
                        event_name = GetLockNameFromTagType(eventId);
                        break;
                case PG_WAIT_EXTENSION:
-                       event_name = GetWaitEventExtensionIdentifier(eventId);
+               case PG_WAIT_INJECTIONPOINT:
+                       event_name = 
GetWaitEventCustomIdentifier(wait_event_info);
                        break;
                case PG_WAIT_BUFFERPIN:
                        {
diff --git a/src/backend/utils/activity/wait_event_funcs.c 
b/src/backend/utils/activity/wait_event_funcs.c
index ba244c2..fa8bc05 100644
--- a/src/backend/utils/activity/wait_event_funcs.c
+++ b/src/backend/utils/activity/wait_event_funcs.c
@@ -48,7 +48,7 @@ pg_get_wait_events(PG_FUNCTION_ARGS)
 #define PG_GET_WAIT_EVENTS_COLS 3
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
        char      **waiteventnames;
-       int                     nbextwaitevents;
+       int                     nbwaitevents;
 
        /* Build tuplestore to hold the result rows */
        InitMaterializedSRF(fcinfo, 0);
@@ -67,9 +67,10 @@ pg_get_wait_events(PG_FUNCTION_ARGS)
        }
 
        /* Handle custom wait events for extensions */
-       waiteventnames = GetWaitEventExtensionNames(&nbextwaitevents);
+       waiteventnames = GetWaitEventCustomNames(PG_WAIT_EXTENSION,
+                                                                               
         &nbwaitevents);
 
-       for (int idx = 0; idx < nbextwaitevents; idx++)
+       for (int idx = 0; idx < nbwaitevents; idx++)
        {
                StringInfoData buf;
                Datum           values[PG_GET_WAIT_EVENTS_COLS] = {0};
@@ -89,5 +90,29 @@ pg_get_wait_events(PG_FUNCTION_ARGS)
                tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, 
values, nulls);
        }
 
+       /* Likewise for injection points */
+       waiteventnames = GetWaitEventCustomNames(PG_WAIT_INJECTIONPOINT,
+                                                                               
         &nbwaitevents);
+
+       for (int idx = 0; idx < nbwaitevents; idx++)
+       {
+               StringInfoData buf;
+               Datum           values[PG_GET_WAIT_EVENTS_COLS] = {0};
+               bool            nulls[PG_GET_WAIT_EVENTS_COLS] = {0};
+
+
+               values[0] = CStringGetTextDatum("InjectionPoint");
+               values[1] = CStringGetTextDatum(waiteventnames[idx]);
+
+               initStringInfo(&buf);
+               appendStringInfo(&buf,
+                                                "Waiting for injection point 
\"%s\"",
+                                                waiteventnames[idx]);
+
+               values[2] = CStringGetTextDatum(buf.data);
+
+               tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, 
values, nulls);
+       }
+
        return (Datum) 0;
 }
diff --git a/src/backend/utils/activity/wait_event_names.txt 
b/src/backend/utils/activity/wait_event_names.txt
index 87cbca2..db37bee 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -340,7 +340,7 @@ LogicalRepWorker    "Waiting to read or update the state of 
logical replication wor
 XactTruncation "Waiting to execute <function>pg_xact_status</function> or 
update the oldest transaction ID available to it."
 WrapLimitsVacuum       "Waiting to update limits on transaction id and 
multixact consumption."
 NotifyQueueTail        "Waiting to update limit on <command>NOTIFY</command> 
message storage."
-WaitEventExtension     "Waiting to read or update custom wait events 
information for extensions."
+WaitEventCustom        "Waiting to read or update custom wait events 
information."
 WALSummarizer  "Waiting to read or update WAL summarization state."
 DSMRegistry    "Waiting to read or update the dynamic shared memory registry."
 InjectionPoint "Waiting to read or update information related to injection 
points."
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 85f6568..6a2f64c 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -78,7 +78,7 @@ PG_LWLOCK(44, XactTruncation)
 /* 45 was XactTruncationLock until removal of BackendRandomLock */
 PG_LWLOCK(46, WrapLimitsVacuum)
 PG_LWLOCK(47, NotifyQueueTail)
-PG_LWLOCK(48, WaitEventExtension)
+PG_LWLOCK(48, WaitEventCustom)
 PG_LWLOCK(49, WALSummarizer)
 PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 1b735d4..9f18a75 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -24,6 +24,7 @@
 #define PG_WAIT_IPC                                    0x08000000U
 #define PG_WAIT_TIMEOUT                                0x09000000U
 #define PG_WAIT_IO                                     0x0A000000U
+#define PG_WAIT_INJECTIONPOINT         0x0B000000U
 
 /* enums for wait events */
 #include "utils/wait_event_types.h"
@@ -38,26 +39,28 @@ extern void pgstat_reset_wait_event_storage(void);
 extern PGDLLIMPORT uint32 *my_wait_event_info;
 
 
-/* ----------
- * Wait Events - Extension
+/*
+ * Wait Events - Extension, InjectionPoint
  *
- * Use this category when the server process is waiting for some condition
- * defined by an extension module.
+ * Use InjectionPoint when the server process is waiting in an injection
+ * point.  Use Extension for other cases of the server process waiting for
+ * some condition defined by an extension module.
  *
- * Extensions can define their own wait events in this category.  They should
- * call WaitEventExtensionNew() with a wait event string.  If the wait event
- * associated to a string is already allocated, it returns the wait event
- * information to use.  If not, it gets one wait event ID allocated from
+ * Extensions can define their own wait events in these categories.  They
+ * should call one of these functions with a wait event string.  If the wait
+ * event associated to a string is already allocated, it returns the wait
+ * event information to use.  If not, it gets one wait event ID allocated from
  * a shared counter, associates the string to the ID in the shared dynamic
  * hash and returns the wait event information.
  *
  * The ID retrieved can be used with pgstat_report_wait_start() or equivalent.
  */
-extern void WaitEventExtensionShmemInit(void);
-extern Size WaitEventExtensionShmemSize(void);
-
 extern uint32 WaitEventExtensionNew(const char *wait_event_name);
-extern char **GetWaitEventExtensionNames(int *nwaitevents);
+extern uint32 WaitEventInjectionPointNew(const char *wait_event_name);
+
+extern void WaitEventCustomShmemInit(void);
+extern Size WaitEventCustomShmemSize(void);
+extern char **GetWaitEventCustomNames(uint32 classId, int *nwaitevents);
 
 /* ----------
  * pgstat_report_wait_start() -
diff --git a/src/test/modules/injection_points/injection_points.c 
b/src/test/modules/injection_points/injection_points.c
index 5c44625..1b695a1 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -216,7 +216,7 @@ injection_wait(const char *name, const void *private_data)
         * this custom wait event name is not released, but we don't care much 
for
         * testing as this should be short-lived.
         */
-       injection_wait_event = WaitEventExtensionNew(name);
+       injection_wait_event = WaitEventInjectionPointNew(name);
 
        /*
         * Find a free slot to wait for, and register this injection point's 
name.
diff --git a/src/test/regress/expected/sysviews.out 
b/src/test/regress/expected/sysviews.out
index dbfd0c13..2176a54 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -158,9 +158,10 @@ select name, setting from pg_settings where name like 
'enable%';
  enable_tidscan                 | on
 (22 rows)
 
--- There are always wait event descriptions for various types.
+-- There are always wait event descriptions for various types.  InjectionPoint
+-- may be present or absent, depending on history since last postmaster start.
 select type, count(*) > 0 as ok FROM pg_wait_events
-  group by type order by type COLLATE "C";
+  where type <> 'InjectionPoint' group by type order by type COLLATE "C";
    type    | ok 
 -----------+----
  Activity  | t
diff --git a/src/test/regress/sql/sysviews.sql 
b/src/test/regress/sql/sysviews.sql
index c4f59dd..b047fb5 100644
--- a/src/test/regress/sql/sysviews.sql
+++ b/src/test/regress/sql/sysviews.sql
@@ -70,9 +70,10 @@ select count(*) = 0 as ok from pg_stat_wal_receiver;
 -- a regression test run.
 select name, setting from pg_settings where name like 'enable%';
 
--- There are always wait event descriptions for various types.
+-- There are always wait event descriptions for various types.  InjectionPoint
+-- may be present or absent, depending on history since last postmaster start.
 select type, count(*) > 0 as ok FROM pg_wait_events
-  group by type order by type COLLATE "C";
+  where type <> 'InjectionPoint' group by type order by type COLLATE "C";
 
 -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
 -- more-or-less working.  We can't test their contents in any great detail
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 61ad417..75433b3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3099,9 +3099,9 @@ WaitEvent
 WaitEventActivity
 WaitEventBufferPin
 WaitEventClient
-WaitEventExtensionCounterData
-WaitEventExtensionEntryById
-WaitEventExtensionEntryByName
+WaitEventCustomCounterData
+WaitEventCustomEntryByInfo
+WaitEventCustomEntryByName
 WaitEventIO
 WaitEventIPC
 WaitEventSet

Reply via email to