Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1dKuvv-0004Vy-I0 for pgsql-bugs@arkaria.postgresql.org; Tue, 13 Jun 2017 23:09:11 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84_2) (envelope-from ) id 1dKuvu-0007Zf-Qr for pgsql-bugs@arkaria.postgresql.org; Tue, 13 Jun 2017 23:09:10 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1dKuvC-0006KH-4z for pgsql-bugs@postgresql.org; Tue, 13 Jun 2017 23:08:26 +0000 Received: from mail-qk0-x22b.google.com ([2607:f8b0:400d:c09::22b]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.84_2) (envelope-from ) id 1dKuv7-0006OA-RR for pgsql-bugs@postgresql.org; Tue, 13 Jun 2017 23:08:25 +0000 Received: by mail-qk0-x22b.google.com with SMTP id d14so24142009qkb.1 for ; Tue, 13 Jun 2017 16:08:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:mime-version:content-disposition :content-transfer-encoding:in-reply-to:user-agent; bh=PpR7atIgBA53Dqa3XEMWyb7QNmDAoiZIvropy1ddVgI=; b=b9B8nki3MFFn8u8+cnG04C2b8GgIRpaX1wfU7hrPaj+32JPRpghNl19RZ1Rz4bS3KU PB9E+lQOZ5UVToe+UjpGgDQc1Qv8TphRwF0ke73GdHBqO9cgzop/aCC+pZkPiu+ac1aY HJiD4L3O2hyI38ASGn7KQgJpXaVrFurz1UOK/f41jzMxNA1jIiGX0chq79vdnC+ycTkD xrFTjzYCEXM3gsFRe1PAfysubwKQyGsNLmQL7f2lrVz8TAJoBReuYgyzUDuxZkZB6eJQ UvuuD6SuGX46HF2FQ/vARJcJRwWp9DC5KQUeeZSK/Tfm9b9c0/BZVRr1e6mpQKUXfTIq afvg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition:content-transfer-encoding:in-reply-to :user-agent; bh=PpR7atIgBA53Dqa3XEMWyb7QNmDAoiZIvropy1ddVgI=; b=P8eOHN81eJ4E+uLAj7pj9OH6oFiDCWABrqNsEV3loG4OKqlK95qmQnfnV/FMUv/jLI u1XMU54slga3GDggiePbTZ63CIiXsON3S0H8Et3owlaWLjBhiBwwLy8Mj2O+GbytF9oz OKiFi/+8COnOi6QipJpwc2FBPwAG+wZfcPr6LIKZ1CX2a0OoBLCC53Oi2Ag12tcFx29Y Xsgl6YsEdSQbFyAmLGKbrhVVF9wVZ4M9pLOgLt+L73zczT+H3qjQYFVfmaKVVPh5/b6O grsJKXoAYE5Xoy1BDKQpVbKfnJZh8mBldfka8L8NhfsMV2a248PBRuZtsALIXAJrRLfs XCpw== X-Gm-Message-State: AKS2vOxZWhSqVvFNTw/8ymTo6BNtTFqiaZT+LH48aral1+JKo057T58z 7HekFEUVN87CaaO1 X-Received: by 10.55.46.2 with SMTP id u2mr2915412qkh.153.1497395300054; Tue, 13 Jun 2017 16:08:20 -0700 (PDT) Received: from alvin.alvh.no-ip.org ([190.217.209.130]) by smtp.gmail.com with ESMTPSA id g58sm10634056qtb.23.2017.06.13.16.08.17 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 13 Jun 2017 16:08:19 -0700 (PDT) Received: by alvin.alvh.no-ip.org (Postfix, from userid 1000) id 26425517; Tue, 13 Jun 2017 20:08:12 -0300 (-03) Date: Tue, 13 Jun 2017 19:08:12 -0400 From: Alvaro Herrera To: Michael Paquier Cc: wangchuanting , PostgreSQL mailing lists Subject: Re: Re: BUG #14680: startup process on standby encounter a deadlock of TwoPhaseStateLock when redo 2PC xlog Message-ID: <20170613230812.uvlhjlvfbad7njb7@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="ay53zpangzel2gbx" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: User-Agent: NeoMutt/20170306-137-4415bd-dirty (1.8.0) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-bugs Precedence: bulk Sender: pgsql-bugs-owner@postgresql.org --ay53zpangzel2gbx Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit Michael Paquier wrote: > The current coding is actually safe because the checkpointer does not > remove or add any 2PC entry in the array while holding > TwoPhaseStateLock, it just updates some values that need to be read > and/or written while holding the lock. Well, to be honest, HEAD is > wrong because it can read a flag value while the checkpointer updates > it, and the patch is careful to change that to be correct. The wrong > part is when calling ProcessTwoPhaseBuffer() in > RecoverPreparedTransactions() which accesses gxact->ondisk and > prepare_start_lsn without locking things. Honestly I don't like this rationale very much. Even if doing the unlocked access is safe today, it looks like installing a landmine for the future, and for what? I don't think there's a lot to be gained: RecoverPreparedTransactions only runs once in the life of a server, and CheckPointTwoPhase is supposed to have a very short runtime (per explanation in comments therein). It seems better to me to continue our tradition of using the appropriate locks instead of playing a dangerous game. So I propose that RecoverPreparedTransactions grabs exclusive lock at the top, and only the bottom part of the loop is done unlocked, which AFAICS should be safe. (MarkAsPrepared gained a boolean argument indicating that caller already holds lock). Here's a patch along those lines. The full testsuite is running now, but the recovery tests pass fine. -- Álvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --ay53zpangzel2gbx Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="2pc-redo-lwlock-fix-v6.patch" diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index c50f9c4bf6..e9751aa2f6 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -195,7 +195,10 @@ typedef struct TwoPhaseStateData static TwoPhaseStateData *TwoPhaseState; /* - * Global transaction entry currently locked by us, if any. + * Global transaction entry currently locked by us, if any. Note that any + * access to the entry pointed to by this variable must be protected by + * TwoPhaseStateLock, though obviously the pointer itself doesn't need to be + * (since it's just local memory). */ static GlobalTransaction MyLockedGxact = NULL; @@ -338,18 +341,13 @@ AtAbort_Twophase(void) * resources held by the transaction yet. In those cases, the in-memory * state can be wrong, but it's too late to back out. */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); if (!MyLockedGxact->valid) - { RemoveGXact(MyLockedGxact); - } else - { - LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); - MyLockedGxact->locking_backend = InvalidBackendId; + LWLockRelease(TwoPhaseStateLock); - LWLockRelease(TwoPhaseStateLock); - } MyLockedGxact = NULL; } @@ -454,6 +452,8 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, PGXACT *pgxact; int i; + Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); + Assert(gxact != NULL); proc = &ProcGlobal->allProcs[gxact->pgprocno]; pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; @@ -530,15 +530,19 @@ GXactLoadSubxactData(GlobalTransaction gxact, int nsubxacts, /* * MarkAsPrepared * Mark the GXACT as fully valid, and enter it into the global ProcArray. + * + * lock_held indicates whether caller already holds TwoPhaseStateLock. */ static void -MarkAsPrepared(GlobalTransaction gxact) +MarkAsPrepared(GlobalTransaction gxact, bool lock_held) { /* Lock here may be overkill, but I'm not convinced of that ... */ - LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); + if (!lock_held) + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); Assert(!gxact->valid); gxact->valid = true; - LWLockRelease(TwoPhaseStateLock); + if (!lock_held) + LWLockRelease(TwoPhaseStateLock); /* * Put it into the global ProcArray so TransactionIdIsInProgress considers @@ -632,7 +636,7 @@ RemoveGXact(GlobalTransaction gxact) { int i; - LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); + Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { @@ -646,14 +650,10 @@ RemoveGXact(GlobalTransaction gxact) gxact->next = TwoPhaseState->freeGXacts; TwoPhaseState->freeGXacts = gxact; - LWLockRelease(TwoPhaseStateLock); - return; } } - LWLockRelease(TwoPhaseStateLock); - elog(ERROR, "failed to find %p in GlobalTransaction array", gxact); } @@ -1127,7 +1127,7 @@ EndPrepare(GlobalTransaction gxact) * the xact crashed. Instead we have a window where the same XID appears * twice in ProcArray, which is OK. */ - MarkAsPrepared(gxact); + MarkAsPrepared(gxact, false); /* * Now we can mark ourselves as out of the commit critical section: a @@ -1506,7 +1506,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit) if (gxact->ondisk) RemoveTwoPhaseFile(xid, true); + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); RemoveGXact(gxact); + LWLockRelease(TwoPhaseStateLock); MyLockedGxact = NULL; pfree(buf); @@ -1734,6 +1736,7 @@ restoreTwoPhaseData(void) struct dirent *clde; cldir = AllocateDir(TWOPHASE_DIR); + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); while ((clde = ReadDir(cldir, TWOPHASE_DIR)) != NULL) { if (strlen(clde->d_name) == 8 && @@ -1752,6 +1755,7 @@ restoreTwoPhaseData(void) PrepareRedoAdd(buf, InvalidXLogRecPtr, InvalidXLogRecPtr); } } + LWLockRelease(TwoPhaseStateLock); FreeDir(cldir); } @@ -1792,7 +1796,7 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p) int allocsize = 0; int i; - LWLockAcquire(TwoPhaseStateLock, LW_SHARED); + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { TransactionId xid; @@ -1867,7 +1871,7 @@ StandbyRecoverPreparedTransactions(void) { int i; - LWLockAcquire(TwoPhaseStateLock, LW_SHARED); + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { TransactionId xid; @@ -1893,7 +1897,8 @@ StandbyRecoverPreparedTransactions(void) * Scan the shared memory entries of TwoPhaseState and reload the state for * each prepared transaction (reacquire locks, etc). * - * This is run during database startup. + * This is run at the end of recovery, but before we allow backends to write + * WAL. * * At the end of recovery the way we take snapshots will change. We now need * to mark all running transactions with their full SubTransSetParent() info @@ -1907,9 +1912,7 @@ RecoverPreparedTransactions(void) { int i; - /* - * Don't need a lock in the recovery phase. - */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { TransactionId xid; @@ -1955,7 +1958,6 @@ RecoverPreparedTransactions(void) * Recreate its GXACT and dummy PGPROC. But, check whether it was * added in redo and already has a shmem entry for it. */ - LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); MarkAsPreparingGuts(gxact, xid, gid, hdr->prepared_at, hdr->owner, hdr->database); @@ -1963,13 +1965,13 @@ RecoverPreparedTransactions(void) /* recovered, so reset the flag for entries generated by redo */ gxact->inredo = false; + GXactLoadSubxactData(gxact, hdr->nsubxacts, subxids); + MarkAsPrepared(gxact, true); + LWLockRelease(TwoPhaseStateLock); - GXactLoadSubxactData(gxact, hdr->nsubxacts, subxids); - MarkAsPrepared(gxact); - /* - * Recover other state (notably locks) using resource managers + * Recover other state (notably locks) using resource managers. */ ProcessRecords(bufptr, xid, twophase_recover_callbacks); @@ -1988,7 +1990,11 @@ RecoverPreparedTransactions(void) PostPrepare_Twophase(); pfree(buf); + + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); } + + LWLockRelease(TwoPhaseStateLock); } /* @@ -2014,6 +2020,8 @@ ProcessTwoPhaseBuffer(TransactionId xid, TwoPhaseFileHeader *hdr; int i; + Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); + if (!fromdisk) Assert(prepare_start_lsn != InvalidXLogRecPtr); @@ -2030,8 +2038,8 @@ ProcessTwoPhaseBuffer(TransactionId xid, else { ereport(WARNING, - (errmsg("removing stale two-phase state from" - " shared memory for \"%u\"", xid))); + (errmsg("removing stale two-phase state from shared memory for \"%u\"", + xid))); PrepareRedoRemove(xid, true); } return NULL; @@ -2308,6 +2316,7 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn) const char *gid; GlobalTransaction gxact; + Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); Assert(RecoveryInProgress()); bufptr = buf + MAXALIGN(sizeof(TwoPhaseFileHeader)); @@ -2324,7 +2333,6 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn) * that it got added in the redo phase */ - LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); /* Get a free gxact from the freelist */ if (TwoPhaseState->freeGXacts == NULL) ereport(ERROR, @@ -2350,17 +2358,17 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn) Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts); TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = gxact; - LWLockRelease(TwoPhaseStateLock); - - elog(DEBUG2, "Adding 2PC data to shared memory %u", gxact->xid); + elog(DEBUG2, "added 2PC data in shared memory for transaction %u", gxact->xid); } /* * PrepareRedoRemove * - * Remove the corresponding gxact entry from TwoPhaseState. Also - * remove the 2PC file if a prepared transaction was saved via - * an earlier checkpoint. + * Remove the corresponding gxact entry from TwoPhaseState. Also remove + * the 2PC file if a prepared transaction was saved via an earlier checkpoint. + * + * Caller must hold TwoPhaseStateLock in exclusive mode, because TwoPhaseState + * is updated. */ void PrepareRedoRemove(TransactionId xid, bool giveWarning) @@ -2369,9 +2377,9 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning) int i; bool found = false; + Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); Assert(RecoveryInProgress()); - LWLockAcquire(TwoPhaseStateLock, LW_SHARED); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { gxact = TwoPhaseState->prepXacts[i]; @@ -2383,7 +2391,6 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning) break; } } - LWLockRelease(TwoPhaseStateLock); /* * Just leave if there is nothing, this is expected during WAL replay. @@ -2394,7 +2401,7 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning) /* * And now we can clean up any files we may have left. */ - elog(DEBUG2, "Removing 2PC data from shared memory %u", xid); + elog(DEBUG2, "removing 2PC data for transaction %u", xid); if (gxact->ondisk) RemoveTwoPhaseFile(xid, giveWarning); RemoveGXact(gxact); diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 7e8c598f2a..e09696c37f 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -5351,6 +5351,8 @@ xact_redo_commit(xl_xact_parsed_commit *parsed, int i; TimestampTz commit_time; + Assert(TransactionIdIsValid(xid)); + max_xid = TransactionIdLatest(xid, parsed->nsubxacts, parsed->subxacts); /* @@ -5518,6 +5520,8 @@ xact_redo_abort(xl_xact_parsed_abort *parsed, TransactionId xid) int i; TransactionId max_xid; + Assert(TransactionIdIsValid(xid)); + /* * Make sure nextXid is beyond any XID mentioned in the record. * @@ -5598,51 +5602,49 @@ xact_redo(XLogReaderState *record) /* Backup blocks are not used in xact records */ Assert(!XLogRecHasAnyBlockRefs(record)); - if (info == XLOG_XACT_COMMIT || info == XLOG_XACT_COMMIT_PREPARED) + if (info == XLOG_XACT_COMMIT) { xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record); xl_xact_parsed_commit parsed; - ParseCommitRecord(XLogRecGetInfo(record), xlrec, - &parsed); - - if (info == XLOG_XACT_COMMIT) - { - Assert(!TransactionIdIsValid(parsed.twophase_xid)); - xact_redo_commit(&parsed, XLogRecGetXid(record), - record->EndRecPtr, XLogRecGetOrigin(record)); - } - else - { - Assert(TransactionIdIsValid(parsed.twophase_xid)); - xact_redo_commit(&parsed, parsed.twophase_xid, - record->EndRecPtr, XLogRecGetOrigin(record)); - - /* Delete TwoPhaseState gxact entry and/or 2PC file. */ - PrepareRedoRemove(parsed.twophase_xid, false); - } + ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed); + xact_redo_commit(&parsed, XLogRecGetXid(record), + record->EndRecPtr, XLogRecGetOrigin(record)); } - else if (info == XLOG_XACT_ABORT || info == XLOG_XACT_ABORT_PREPARED) + else if (info == XLOG_XACT_COMMIT_PREPARED) + { + xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record); + xl_xact_parsed_commit parsed; + + ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed); + xact_redo_commit(&parsed, parsed.twophase_xid, + record->EndRecPtr, XLogRecGetOrigin(record)); + + /* Delete TwoPhaseState gxact entry and/or 2PC file. */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); + PrepareRedoRemove(parsed.twophase_xid, false); + LWLockRelease(TwoPhaseStateLock); + } + else if (info == XLOG_XACT_ABORT) { xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record); xl_xact_parsed_abort parsed; - ParseAbortRecord(XLogRecGetInfo(record), xlrec, - &parsed); + ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed); + xact_redo_abort(&parsed, XLogRecGetXid(record)); + } + else if (info == XLOG_XACT_ABORT_PREPARED) + { + xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record); + xl_xact_parsed_abort parsed; - if (info == XLOG_XACT_ABORT) - { - Assert(!TransactionIdIsValid(parsed.twophase_xid)); - xact_redo_abort(&parsed, XLogRecGetXid(record)); - } - else - { - Assert(TransactionIdIsValid(parsed.twophase_xid)); - xact_redo_abort(&parsed, parsed.twophase_xid); + ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed); + xact_redo_abort(&parsed, parsed.twophase_xid); - /* Delete TwoPhaseState gxact entry and/or 2PC file. */ - PrepareRedoRemove(parsed.twophase_xid, false); - } + /* Delete TwoPhaseState gxact entry and/or 2PC file. */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); + PrepareRedoRemove(parsed.twophase_xid, false); + LWLockRelease(TwoPhaseStateLock); } else if (info == XLOG_XACT_PREPARE) { @@ -5650,9 +5652,11 @@ xact_redo(XLogReaderState *record) * Store xid and start/end pointers of the WAL record in TwoPhaseState * gxact entry. */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); PrepareRedoAdd(XLogRecGetData(record), record->ReadRecPtr, record->EndRecPtr); + LWLockRelease(TwoPhaseStateLock); } else if (info == XLOG_XACT_ASSIGNMENT) { --ay53zpangzel2gbx Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-bugs --ay53zpangzel2gbx--