diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2024-04-02 14:59:04 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2024-04-02 14:59:04 -0400 |
commit | cbfbb14bd7de9e90fb107cdcaa783264bf8fcdb9 (patch) | |
tree | bd48e7703dbaaaa0fa2e5ed643b02fe31a642061 | |
parent | 3235a11c128b5979bce84d56b8044c7fb2e6b61d (diff) | |
download | postgresql-cbfbb14bd7de9e90fb107cdcaa783264bf8fcdb9.tar.gz postgresql-cbfbb14bd7de9e90fb107cdcaa783264bf8fcdb9.zip |
Avoid deadlock during orphan temp table removal.
If temp tables have dependencies (such as sequences) then it's
possible for autovacuum's cleanup of orphan temp tables to deadlock
against an incoming backend that's trying to clean out the temp
namespace for its own use. That can happen because RemoveTempRelations'
performDeletion call can visit objects within the namespace in
an order different from the order in which a per-table deletion
will visit them.
To fix, observe that performDeletion will begin by taking an exclusive
lock on the temp namespace (even though it won't actually delete it).
So, if we can get a shared lock on the namespace, we can be sure we're
not running concurrently with RemoveTempRelations, while also not
conflicting with ordinary use of the namespace. This requires
introducing a conditional version of LockDatabaseObject, but that's no
big deal. (It's surprising we've got along without that this long.)
Report and patch by Mikhail Zhilin. Back-patch to all supported
branches.
Discussion: https://postgr.es/m/c43ce028-2bc2-4865-9b89-3f706246eed5@postgrespro.ru
-rw-r--r-- | src/backend/postmaster/autovacuum.c | 21 | ||||
-rw-r--r-- | src/backend/storage/lmgr/lmgr.c | 38 | ||||
-rw-r--r-- | src/include/storage/lmgr.h | 2 |
3 files changed, 60 insertions, 1 deletions
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index ae9be9b9113..7dd9345c617 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -76,6 +76,7 @@ #include "catalog/dependency.h" #include "catalog/namespace.h" #include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" #include "lib/ilist.h" @@ -2329,6 +2330,24 @@ do_autovacuum(void) continue; } + /* + * Try to lock the temp namespace, too. Even though we have lock on + * the table itself, there's a risk of deadlock against an incoming + * backend trying to clean out the temp namespace, in case this table + * has dependencies (such as sequences) that the backend's + * performDeletion call might visit in a different order. If we can + * get AccessShareLock on the namespace, that's sufficient to ensure + * we're not running concurrently with RemoveTempRelations. If we + * can't, back off and let RemoveTempRelations do its thing. + */ + if (!ConditionalLockDatabaseObject(NamespaceRelationId, + classForm->relnamespace, 0, + AccessShareLock)) + { + UnlockRelationOid(relid, AccessExclusiveLock); + continue; + } + /* OK, let's delete it */ ereport(LOG, (errmsg("autovacuum: dropping orphan temp table \"%s.%s.%s\"", @@ -2346,7 +2365,7 @@ do_autovacuum(void) /* * To commit the deletion, end current transaction and start a new - * one. Note this also releases the lock we took. + * one. Note this also releases the locks we took. */ CommitTransactionCommand(); StartTransactionCommand(); diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index ee9b89a6726..2da91eb492d 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -1020,6 +1020,44 @@ LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, } /* + * ConditionalLockDatabaseObject + * + * As above, but only lock if we can get the lock without blocking. + * Returns true iff the lock was acquired. + */ +bool +ConditionalLockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, + LOCKMODE lockmode) +{ + LOCKTAG tag; + LOCALLOCK *locallock; + LockAcquireResult res; + + SET_LOCKTAG_OBJECT(tag, + MyDatabaseId, + classid, + objid, + objsubid); + + res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock); + + if (res == LOCKACQUIRE_NOT_AVAIL) + return false; + + /* + * Now that we have the lock, check for invalidation messages; see notes + * in LockRelationOid. + */ + if (res != LOCKACQUIRE_ALREADY_CLEAR) + { + AcceptInvalidationMessages(); + MarkLockClear(locallock); + } + + return true; +} + +/* * UnlockDatabaseObject */ void diff --git a/src/include/storage/lmgr.h b/src/include/storage/lmgr.h index 4ee91e3cf93..8ab833d1d4a 100644 --- a/src/include/storage/lmgr.h +++ b/src/include/storage/lmgr.h @@ -93,6 +93,8 @@ extern void SpeculativeInsertionWait(TransactionId xid, uint32 token); /* Lock a general object (other than a relation) of the current database */ extern void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode); +extern bool ConditionalLockDatabaseObject(Oid classid, Oid objid, + uint16 objsubid, LOCKMODE lockmode); extern void UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode); |