On 18/05/2026 15:14, Bertrand Drouvot wrote:
On Wed, May 13, 2026 at 04:20:21PM -0400, Robert Haas wrote:
On Tue, Apr 28, 2026 at 7:17 AM Bertrand Drouvot
<[email protected]> wrote:
0003: Add Assert guard to detect permission check before lock regressions
Add instrumentation under USE_ASSERT_CHECKING to detect cases where
object_aclcheck()
is called on a referenced object before a lock is held on it, which would widen
the TOCTOU window between the permission check and the dependency recording.
I really like the idea of having some kind of cross-check system that
can detect future (or current) coding mistakes.
Thanks for the feedback! BTW, it detected a new one due to 4793fc41f82, so v21
attached does fix it to make the CI green.
I had a closer look at patch 0001. It doesn't fix all the problems, we
definitely need patch 0002/0003 too, but it's a good step forward and I
think it makes sense to commit it independently. So I'm focusing on that
now.
I noticed we are already doing essentially the same thing for shared
dependencies, in shdepLockAndCheckObject(). I see that you even copied
the function comment from shdepLockAndCheckObject() to
LockNotPinnedObject(), but I didn't see it being otherwise mentioned in
this thread. In any case, that's a good argument for doing the same for
non-shared dependencies that we already do for shared dependencies.
+ if (object->classId == RelationRelationId)
+ {
+ /* skip shared relations as they are pinned */
+ if (IsSharedRelation(object->objectId))
+ return;
+
+ /*
+ * We must be in one of the two following cases that would
already
+ * prevent the relation to be dropped: 1. The relation is
already
+ * locked (could be an existing relation or a relation that we
are
+ * creating). 2. The relation is protected indirectly (i.e an
index
+ * protected by a lock on its table, a table protected by a
lock on a
+ * function that depends of the table...). To avoid any risks,
acquire
+ * a lock if there is none. That may add unnecessary lock for
2. but
+ * that's worth it.
+ */
+ if (!CheckRelationOidLockedByMe(object->objectId,
AccessShareLock, true))
+ LockRelationOid(object->objectId, AccessShareLock);
+ return;
+ }
Hmm, shouldn't that re-check that the relation exists, after acquiring
the lock, like the non-relation codepath does? Otherwise it's a little
pointless to acquire the lock.
I did a bunch of little refactorings and ended up with the attached.
Notable changes:
- I renamed and moved LockNotPinnedObject() into
dependencyLockAndCheckObject(), for consistency with
shdepLockAndCheckObject().
- Removed ObjectByIdExist(), inlined it into the caller. The argument to
use SnapshotSelf or not seemed a bit too special to be generally useful
- Changed the error message and code to rhyme with the existing "role
<OID> was concurrently dropped" errors. I didn't add the OID to the
error message however, because that makes the testing hard. It'd be nice
to have a general masking mechanism for OIDs and XIDs in error messages
in tests, but now is not the time for that.
- Added a test for a dependency on a role too, to cover the existing
shdepLockAndCheckObject() function. It's currently disabled because it
prints the OID, though.
- Heikki
From d51523d9003c902f3968e174f158c27680eab427 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Mon, 27 Apr 2026 14:01:46 +0000
Subject: [PATCH v22 1/1] Avoid orphaned objects dependencies
Concurrent DDL can create orphaned dependencies in pg_depend, objects
referencing other objects that no longer exist. For example:
Scenario 1:
session 1: begin; drop schema schem;
session 2: create a function in the schema schem
session 1: commit;
With the above, the function created in session 2 would be linked to a non
existing schema.
Scenario 2:
session 1: begin; create a function in the schema schem
session 2: drop schema schem;
session 1: commit;
With the above, the function created in session 1 would be linked to a non
existing schema.
Fix by acquiring AccessShareLock on referenced objects when recording
dependencies. This conflicts with AccessExclusiveLock taken by DROP,
preventing the race. After acquiring the lock, verify the object still
exists, if it was dropped concurrently, report an error.
The lock and check is done in both recordMultipleDependencies() and
changeDependencyFor().
The patch adds a few tests for some dependency cases (that would currently produce
orphaned objects):
- schema and function (as the above scenarios)
- alter a dependency (function and schema)
- function and arg type
- function and return type
- function and function
- domain and domain
- table and type
- server and foreign data wrapper
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/backend/catalog/pg_depend.c | 130 +++++++++++++++++
.../expected/test_dependencies_locks.out | 137 ++++++++++++++++++
src/test/isolation/isolation_schedule | 1 +
.../specs/test_dependencies_locks.spec | 108 ++++++++++++++
src/test/regress/expected/alter_table.out | 11 +-
5 files changed, 382 insertions(+), 5 deletions(-)
create mode 100644 src/test/isolation/expected/test_dependencies_locks.out
create mode 100644 src/test/isolation/specs/test_dependencies_locks.spec
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 07c2d41c189..ff17d5850c5 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -19,6 +19,7 @@
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
+#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
@@ -27,13 +28,17 @@
#include "catalog/partition.h"
#include "commands/extension.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
+#include "storage/lock.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
static bool isObjectPinned(const ObjectAddress *object);
+static void dependencyLockAndCheckObject(Oid classId, Oid objectId);
/*
@@ -109,6 +114,12 @@ recordMultipleDependencies(const ObjectAddress *depender,
if (isObjectPinned(referenced))
continue;
+ /*
+ * Acquire a lock and check object still exists while recording the
+ * dependency.
+ */
+ dependencyLockAndCheckObject(referenced->classId, referenced->objectId);
+
if (slot_init_count < max_slots)
{
slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
@@ -507,6 +518,13 @@ changeDependencyFor(Oid classId, Oid objectId,
return 1;
}
+ /*
+ * Make sure the new referenced object doesn't go away while we record the
+ * dependency.
+ */
+ if (!newIsPinned)
+ dependencyLockAndCheckObject(refClassId, newRefObjectId);
+
depRel = table_open(DependRelationId, RowExclusiveLock);
/* There should be existing dependency record(s), so search. */
@@ -714,6 +732,118 @@ isObjectPinned(const ObjectAddress *object)
}
+/*
+ * dependencyLockAndCheckObject
+ *
+ * Lock the object that we are about to record a dependency on. After it's
+ * locked, verify that it hasn't been dropped while we weren't looking. If
+ * the object has been dropped, an error is thrown.
+ *
+ * If the caller already holds a lock that conflicts with DROP
+ * (AccessShareLock or stronger), this does nothing. That is the desired case
+ * - callers should acquire the lock when they look up the object already -
+ * but many callers are skipping it currently. This is a backstop to make
+ * that we don't record a bogus reference permanently in the catalogs. In the
+ * future, after we have tightened up all the callers, this could just verify
+ * that all the objects are indeed locked and throw an error if not.
+ */
+static void
+dependencyLockAndCheckObject(Oid classId, Oid objectId)
+{
+ /*
+ * Pinned objects cannot be dropped concurrently, and callers checked this
+ * already.
+ */
+ Assert(!IsPinnedObject(classId, objectId));
+
+ if (classId != RelationRelationId)
+ {
+ LOCKTAG tag;
+ SysCacheIdentifier cache;
+ Relation rel;
+ SysScanDesc scan;
+ ScanKeyData skey;
+ HeapTuple tuple;
+
+ SET_LOCKTAG_OBJECT(tag,
+ MyDatabaseId,
+ classId,
+ objectId,
+ 0);
+
+ if (LockHeldByMe(&tag, AccessShareLock, true))
+ return;
+
+ /* Assume we should lock the whole object not a sub-object */
+ LockDatabaseObject(classId, objectId, 0, AccessShareLock);
+
+ /*
+ * Check that the object still exists. If the catalog has a suitable
+ * syscache, check that first.
+ */
+ cache = get_object_catcache_oid(classId);
+ if (cache != SYSCACHEID_INVALID)
+ {
+ if (SearchSysCacheExists1(cache, ObjectIdGetDatum(objectId)))
+ return;
+ }
+
+ /*
+ * If it's not found in the syscache, or there's no suitable syscache
+ * we can use, scan the catalog table using SnapshotSelf. This
+ * handles the case that it's an object we just created (for example,
+ * if it's a composite type created as part of creating a table).
+ */
+ rel = table_open(classId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ get_object_attnum_oid(classId),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(rel, get_object_oid_index(classId),
+ true, SnapshotSelf, 1, &skey);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("dependent %s was concurrently dropped",
+ get_object_class_descr(classId))));
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+ }
+ else
+ {
+ /*
+ * Same logic for pg_class entries, but locking relations is handled
+ * by different functions.
+ *
+ * Callers are more careful with locking relations than other objects,
+ * so we should already have a lock on the relation, or on another
+ * object that indirectly prevents the relation from being dropped.
+ * For example, we might have a strong lock on a table while adding
+ * dependency to its index. However, we cannot detect the indirectly
+ * protected case here easily. To err on the safe side, acquire a
+ * lock directly on the relation if we're not holding one already.
+ */
+
+ /* all shared relations are pinned */
+ Assert(!IsSharedRelation(objectId));
+
+ if (CheckRelationOidLockedByMe(objectId, AccessShareLock, true))
+ return;
+ LockRelationOid(objectId, AccessShareLock);
+
+ if (SearchSysCacheExists1(RELOID, ObjectIdGetDatum(objectId)))
+ return;
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("dependent relation was concurrently dropped")));
+ }
+}
+
/*
* Various special-purpose lookups and manipulations of pg_depend.
*/
diff --git a/src/test/isolation/expected/test_dependencies_locks.out b/src/test/isolation/expected/test_dependencies_locks.out
new file mode 100644
index 00000000000..3bf9e435725
--- /dev/null
+++ b/src/test/isolation/expected/test_dependencies_locks.out
@@ -0,0 +1,137 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_schema: DROP SCHEMA testschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_schema: <... completed>
+ERROR: cannot drop schema testschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_schema: DROP SCHEMA testschema;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_in_schema: <... completed>
+ERROR: dependent schema was concurrently dropped
+
+starting permutation: s1_begin s1_alter_function_schema s2_drop_alterschema s1_commit
+step s1_begin: BEGIN;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema;
+step s2_drop_alterschema: DROP SCHEMA alterschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_alterschema: <... completed>
+ERROR: cannot drop schema alterschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_alterschema s1_alter_function_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_alterschema: DROP SCHEMA alterschema;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema; <waiting ...>
+step s2_commit: COMMIT;
+step s1_alter_function_schema: <... completed>
+ERROR: dependent schema was concurrently dropped
+
+starting permutation: s1_begin s1_create_function_with_argtype s2_drop_foo_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_type: <... completed>
+ERROR: cannot drop type foo because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_argtype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_type: DROP TYPE public.foo;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_argtype: <... completed>
+ERROR: dependent type was concurrently dropped
+
+starting permutation: s1_begin s1_create_function_with_rettype s2_drop_foo_rettype s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1;
+step s2_drop_foo_rettype: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_rettype: <... completed>
+ERROR: cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_rettype s1_create_function_with_rettype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_rettype: DROP DOMAIN id;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_rettype: <... completed>
+ERROR: dependent type was concurrently dropped
+
+starting permutation: s1_begin s1_create_function_with_function s2_drop_function_f s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1;
+step s2_drop_function_f: DROP FUNCTION f(); <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_function_f: <... completed>
+ERROR: cannot drop function f() because other objects depend on it
+
+starting permutation: s2_begin s2_drop_function_f s1_create_function_with_function s2_commit
+step s2_begin: BEGIN;
+step s2_drop_function_f: DROP FUNCTION f();
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_function: <... completed>
+ERROR: dependent function was concurrently dropped
+
+starting permutation: s1_begin s1_create_domain_with_domain s2_drop_domain_id s1_commit
+step s1_begin: BEGIN;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id;
+step s2_drop_domain_id: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_domain_id: <... completed>
+ERROR: cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_domain_id s1_create_domain_with_domain s2_commit
+step s2_begin: BEGIN;
+step s2_drop_domain_id: DROP DOMAIN id;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_domain_with_domain: <... completed>
+ERROR: dependent type was concurrently dropped
+
+starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab);
+step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_footab_type: <... completed>
+ERROR: cannot drop type footab because other objects depend on it
+
+starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_footab_type: DROP TYPE public.footab;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_table_with_type: <... completed>
+ERROR: dependent type was concurrently dropped
+
+starting permutation: s1_begin s1_create_server_with_fdw_wrapper s2_drop_fdw_wrapper s1_commit
+step s1_begin: BEGIN;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_fdw_wrapper: <... completed>
+ERROR: cannot drop foreign-data wrapper fdw_wrapper because other objects depend on it
+
+starting permutation: s2_begin s2_drop_fdw_wrapper s1_create_server_with_fdw_wrapper s2_commit
+step s2_begin: BEGIN;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_server_with_fdw_wrapper: <... completed>
+ERROR: dependent foreign-data wrapper was concurrently dropped
+
+starting permutation: s1_begin s1_alter_function_owner s2_drop_role s1_commit
+step s1_begin: BEGIN;
+step s1_alter_function_owner: ALTER FUNCTION public.falter() OWNER TO regress_dependency;
+step s2_drop_role: DROP ROLE regress_dependency; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_role: <... completed>
+ERROR: role "regress_dependency" cannot be dropped because some objects depend on it
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1578ba191c8..83f626d51b5 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -126,3 +126,4 @@ test: serializable-parallel-3
test: matview-write-skew
test: lock-nowait
test: for-portion-of
+test: test_dependencies_locks
\ No newline at end of file
diff --git a/src/test/isolation/specs/test_dependencies_locks.spec b/src/test/isolation/specs/test_dependencies_locks.spec
new file mode 100644
index 00000000000..fd80c9cb9b7
--- /dev/null
+++ b/src/test/isolation/specs/test_dependencies_locks.spec
@@ -0,0 +1,108 @@
+# Test that concurrent DDL properly prevents orphaned dependencies.
+#
+# When session 1 creates an object that depends on a referenced object,
+# and session 2 concurrently drops that referenced object, the lock
+# acquired during dependency recording must prevent the drop or the
+# create must fail with "dependent object does not exist".
+
+setup
+{
+ CREATE SCHEMA testschema;
+ CREATE SCHEMA alterschema;
+ CREATE TYPE public.foo as enum ('one', 'two');
+ CREATE TYPE public.footab as enum ('three', 'four');
+ CREATE DOMAIN id AS int;
+ CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1;
+ CREATE FUNCTION public.falter() RETURNS int LANGUAGE SQL RETURN 1;
+ CREATE FOREIGN DATA WRAPPER fdw_wrapper;
+ CREATE ROLE regress_dependency;
+}
+
+teardown
+{
+ DROP FUNCTION IF EXISTS testschema.foo();
+ DROP FUNCTION IF EXISTS fooargtype(num foo);
+ DROP FUNCTION IF EXISTS footrettype();
+ DROP FUNCTION IF EXISTS foofunc();
+ DROP FUNCTION IF EXISTS public.falter();
+ DROP FUNCTION IF EXISTS alterschema.falter();
+ DROP DOMAIN IF EXISTS idid;
+ DROP SERVER IF EXISTS srv_fdw_wrapper;
+ DROP TABLE IF EXISTS tabtype;
+ DROP SCHEMA IF EXISTS testschema;
+ DROP SCHEMA IF EXISTS alterschema;
+ DROP TYPE IF EXISTS public.foo;
+ DROP TYPE IF EXISTS public.footab;
+ DROP DOMAIN IF EXISTS id;
+ DROP FUNCTION IF EXISTS f();
+ DROP FOREIGN DATA WRAPPER IF EXISTS fdw_wrapper;
+ DROP ROLE regress_dependency;
+}
+
+session "s1"
+
+step "s1_begin" { BEGIN; }
+step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_argtype" { CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_rettype" { CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; }
+step "s1_create_function_with_function" { CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; }
+step "s1_alter_function_owner" { ALTER FUNCTION public.falter() OWNER TO regress_dependency; }
+step "s1_alter_function_schema" { ALTER FUNCTION public.falter() SET SCHEMA alterschema; }
+step "s1_create_domain_with_domain" { CREATE DOMAIN idid as id; }
+step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); }
+step "s1_create_server_with_fdw_wrapper" { CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+
+step "s2_begin" { BEGIN; }
+step "s2_drop_schema" { DROP SCHEMA testschema; }
+step "s2_drop_alterschema" { DROP SCHEMA alterschema; }
+step "s2_drop_foo_type" { DROP TYPE public.foo; }
+step "s2_drop_foo_rettype" { DROP DOMAIN id; }
+step "s2_drop_footab_type" { DROP TYPE public.footab; }
+step "s2_drop_function_f" { DROP FUNCTION f(); }
+step "s2_drop_domain_id" { DROP DOMAIN id; }
+step "s2_drop_fdw_wrapper" { DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; }
+step "s2_drop_role" { DROP ROLE regress_dependency; }
+step "s2_commit" { COMMIT; }
+
+# function - schema
+permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit"
+permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit"
+
+# alter function - schema
+permutation "s1_begin" "s1_alter_function_schema" "s2_drop_alterschema" "s1_commit"
+permutation "s2_begin" "s2_drop_alterschema" "s1_alter_function_schema" "s2_commit"
+
+# function - argtype
+permutation "s1_begin" "s1_create_function_with_argtype" "s2_drop_foo_type" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_argtype" "s2_commit"
+
+# function - rettype
+permutation "s1_begin" "s1_create_function_with_rettype" "s2_drop_foo_rettype" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_rettype" "s1_create_function_with_rettype" "s2_commit"
+
+# function - function
+permutation "s1_begin" "s1_create_function_with_function" "s2_drop_function_f" "s1_commit"
+permutation "s2_begin" "s2_drop_function_f" "s1_create_function_with_function" "s2_commit"
+
+# domain - domain
+permutation "s1_begin" "s1_create_domain_with_domain" "s2_drop_domain_id" "s1_commit"
+permutation "s2_begin" "s2_drop_domain_id" "s1_create_domain_with_domain" "s2_commit"
+
+# table - type
+permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit"
+permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit"
+
+# server - foreign data wrapper
+permutation "s1_begin" "s1_create_server_with_fdw_wrapper" "s2_drop_fdw_wrapper" "s1_commit"
+permutation "s2_begin" "s2_drop_fdw_wrapper" "s1_create_server_with_fdw_wrapper" "s2_commit"
+
+# function - role
+permutation "s1_begin" "s1_alter_function_owner" "s2_drop_role" "s1_commit"
+
+# XXX: This permutation is disabled because the error message, "role
+# <OID> was concurrently dropped", contains an OID that is not stable.
+#
+# permutation "s2_begin" "s2_drop_role" "s1_alter_function_owner" "s2_commit"
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 6dd22be0e8d..b891d68d4a7 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2949,11 +2949,12 @@ begin;
alter table alterlock2
add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
select * from my_locks order by 1;
- relname | max_lockmode
-------------+-----------------------
- alterlock | ShareRowExclusiveLock
- alterlock2 | ShareRowExclusiveLock
-(2 rows)
+ relname | max_lockmode
+----------------+-----------------------
+ alterlock | ShareRowExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+ alterlock_pkey | AccessShareLock
+(3 rows)
commit;
begin;
--
2.47.3