diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index d89afb09320..af08aecc31c 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -2873,6 +2873,46 @@ LookupGXactBySubid(Oid subid) return found; } +/* + * LookupGXactBySubidHoldingLock + * Check if a prepared transaction done by the apply worker of the given + * subscription holds a lock on the object identified by 'locktag'. + */ +bool +LookupGXactBySubidHoldingLock(Oid subid, const LOCKTAG *locktag) +{ + PGPROC **procs; + int nprocs = 0; + bool found = false; + + procs = palloc_array(PGPROC *, max_prepared_xacts); + + /* + * Collect the matching prepared transactions first, so that we don't take + * a lock partition LWLock while holding TwoPhaseStateLock. A prepared + * transaction may finish before we check its locks, which is fine as the + * result is only used to decide whether to report an error. + */ + LWLockAcquire(TwoPhaseStateLock, LW_SHARED); + for (int i = 0; i < TwoPhaseState->numPrepXacts; i++) + { + GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; + + /* Ignore not-yet-valid GIDs. */ + if (gxact->valid && + IsTwoPhaseTransactionGidForSubid(subid, gxact->gid)) + procs[nprocs++] = GetPGProcByNumber(gxact->pgprocno); + } + LWLockRelease(TwoPhaseStateLock); + + for (int i = 0; i < nprocs && !found; i++) + found = LockHeldByProc(locktag, procs[i]); + + pfree(procs); + + return found; +} + /* * TwoPhaseGetOldestXidInCommit * Return the oldest transaction ID from prepared transactions that are diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index 3a05f85b56e..ade307d22f5 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -544,8 +544,8 @@ get_conflictlog_dest_and_table(int elevel, ConflictLogDest *log_dest) * PREPARED that releases the lock. * * Therefore, with two_phase enabled, don't wait indefinitely. If the lock is - * not immediately available, error out if this subscription has any prepared - * transactions, as one of them may hold the lock. Otherwise, wait with + * not immediately available, error out if a prepared transaction of this + * subscription holds a lock on the relation. Otherwise, wait with * lock_timeout set to DeadlockTimeout + 5s, so that ProcSleep() can run * DeadLockCheck() and interrupt a blocking autovacuum truncation, and so that * the wait ends if a parallel apply worker prepares a transaction meanwhile. @@ -560,6 +560,7 @@ lock_conflictlog_relation(Oid relid) { int save_nestlevel; char buf[32]; + LOCKTAG tag; if (MySubscription->twophasestate != LOGICALREP_TWOPHASE_STATE_ENABLED || LockTimeout > 0) @@ -571,7 +572,9 @@ lock_conflictlog_relation(Oid relid) if (ConditionalLockRelationOid(relid, RowExclusiveLock)) return; - if (LookupGXactBySubid(MySubscription->oid)) + SET_LOCKTAG_RELATION(tag, MyDatabaseId, relid); + + if (LookupGXactBySubidHoldingLock(MySubscription->oid, &tag)) ereport(ERROR, errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on conflict log table of subscription \"%s\"", diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 52fdb656ae8..822a668cf13 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -3078,6 +3078,51 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock) return proclock; } +/* + * LockHeldByProc + * Return true if 'proc' holds a lock, in any mode, on the object + * identified by 'locktag'. + * + * This looks only in the main lock table, so it does not see fast-path + * locks. That is fine for a prepared transaction, as its fast-path locks + * were moved to the main lock table during PREPARE, see AtPrepare_Locks(). + */ +bool +LockHeldByProc(const LOCKTAG *locktag, PGPROC *proc) +{ + uint32 hashcode = LockTagHashCode(locktag); + LWLock *partitionLock = LockHashPartitionLock(hashcode); + LOCK *lock; + bool held = false; + + LWLockAcquire(partitionLock, LW_SHARED); + + lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash, + locktag, + hashcode, + HASH_FIND, + NULL); + if (lock) + { + dlist_iter iter; + + dlist_foreach(iter, &lock->procLocks) + { + PROCLOCK *proclock = dlist_container(PROCLOCK, lockLink, iter.cur); + + if (proclock->tag.myProc == proc && proclock->holdMask != 0) + { + held = true; + break; + } + } + } + + LWLockRelease(partitionLock); + + return held; +} + /* * GetLockConflicts * Get an array of VirtualTransactionIds of xacts currently holding locks diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h index 1d2ff42c9b7..b9beaa69bf5 100644 --- a/src/include/access/twophase.h +++ b/src/include/access/twophase.h @@ -23,6 +23,7 @@ */ typedef struct PGPROC PGPROC; typedef struct VirtualTransactionId VirtualTransactionId; +typedef struct LOCKTAG LOCKTAG; /* * GlobalTransactionData is defined in twophase.c; other places have no @@ -69,6 +70,7 @@ extern bool LookupGXact(const char *gid, XLogRecPtr prepare_end_lsn, extern void TwoPhaseTransactionGid(Oid subid, TransactionId xid, char *gid_res, int szgid); extern bool LookupGXactBySubid(Oid subid); +extern bool LookupGXactBySubidHoldingLock(Oid subid, const LOCKTAG *locktag); extern TransactionId TwoPhaseGetOldestXidInCommit(void); diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index c63c9075466..93493452d0c 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -407,6 +407,7 @@ extern HTAB *GetLockMethodLocalHash(void); #endif extern bool LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock); +extern bool LockHeldByProc(const LOCKTAG *locktag, PGPROC *proc); extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode, int *countp); extern void AtPrepare_Locks(void);