postgres.git / summary / log / commit / refs

commit    7bfaa467193ccb9ef17ba41277dc8985c3c9016d
Author:   Michael Paquier <michael@paquier.xyz>
Date:     Tue Oct 01 06:44:14 2024 +0000
First released in: 18.0, 12.21, 13.17, 14.14, 15.9, 16.5, 17.1

    Fix race condition in COMMIT PREPARED causing orphaned 2PC files
    
    COMMIT PREPARED removes on-disk 2PC files near its end, but the state
    checked if a file is on-disk or not gets read from shared memory while
    not holding the two-phase state lock.
    
    Because of that, there was a small window where a second backend doing a
    PREPARE TRANSACTION could reuse the GlobalTransaction put back into the
    2PC free list by the COMMIT PREPARED, overwriting the "ondisk" flag read
    afterwards by the COMMIT PREPARED to decide if its on-disk two-phase
    state file should be removed, preventing the file deletion.
    
    This commit fixes this issue so as the "ondisk" flag in the
    GlobalTransaction is read while holding the two-phase state lock, not
    from shared memory after its entry has been added to the free list.
    
    Orphaned two-phase state files flushed to disk after a checkpoint are
    discarded at the beginning of recovery.  However, a truncation of
    pg_xact/ would make the startup process issue a FATAL when it cannot
    read the SLRU page holding the state of the transaction whose 2PC file
    was orphaned, which is a necessary step to decide if the 2PC file should
    be removed or not.  Removing manually the file would be necessary in
    this case.
    
    Issue introduced by effe7d9552dd, so backpatch all the way down.
    
    Mea culpa.
    
    Author: wuchengwen
    Discussion: https://postgr.es/m/tencent_A7F059B5136A359625C7B2E4A386B3C3F007@qq.com
    Backpatch-through: 12


src/backend/access/transam/twophase.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 0d57fb25c27..08e7e4ccd2e 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -1487,6 +1487,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) PGPROC *proc; PGXACT *pgxact; TransactionId xid; + bool ondisk; char *buf; char *bufptr; TwoPhaseFileHeader *hdr; @@ -1621,6 +1622,12 @@ FinishPreparedTransaction(const char *gid, bool isCommit) PredicateLockTwoPhaseFinish(xid, isCommit); + /* + * Read this value while holding the two-phase lock, as the on-disk 2PC + * file is physically removed after the lock is released. + */ + ondisk = gxact->ondisk; + /* Clear shared memory state */ RemoveGXact(gxact); @@ -1636,7 +1643,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) /* * And now we can clean up any files we may have left. */ - if (gxact->ondisk) + if (ondisk) RemoveTwoPhaseFile(xid, true); MyLockedGxact = NULL; [parent: 4f6d43c6f8ec]