postgres.git / summary / log / commit / refs

commit    fcc0e27f45e2d8dc6046435908a48626c91d3d84
Author:   Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:     Wed Sep 16 11:52:12 2026 +0000

    Fix multixact members truncation at page boundary
    
    Like in PerformOffsetsTruncation() and e.g. in TruncateSUBTRANS(),
    need to step back to previous page at page boundary, to avoid passing
    a cutoff page to SimpleLruTruncate() that hasn't been created
    yet. Otherwise you can get a bogus 'LOG: could not truncate directory
    "pg_multixact/members": apparent wraparound' message in the log.
    
    Reported-by: Noah Misch <noah@leadboat.com>
    Discussion: https://www.postgresql.org/message-id/20260827231757.78.noahmisch@microsoft.com
    Backpatch-through: 19


src/backend/access/transam/multixact.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index d688815083c..70a4ea69486 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2642,8 +2642,16 @@ MultiXactMemberFreezeThreshold(void) static void PerformMembersTruncation(MultiXactOffset newOldestOffset) { + /* + * We step back one entry to avoid passing a cutoff page that hasn't been + * created yet in the rare case that oldestOffset would be the first item + * on a page and oldestOffset == nextOffset. In that case, if we didn't + * subtract one, we'd trigger SimpleLruTruncate's wraparound detection. + */ + if (newOldestOffset <= 1) + return; SimpleLruTruncate(MultiXactMemberCtl, - MXOffsetToMemberPage(newOldestOffset)); + MXOffsetToMemberPage(newOldestOffset - 1)); } /* @@ -2653,11 +2661,8 @@ static void PerformOffsetsTruncation(MultiXactId newOldestMulti) { /* - * We step back one multixact to avoid passing a cutoff page that hasn't - * been created yet in the rare case that oldestMulti would be the first - * item on a page and oldestMulti == nextMulti. In that case, if we - * didn't subtract one, we'd trigger SimpleLruTruncate's wraparound - * detection. + * Like in PerformMembersTruncation(), step back one entry to avoid + * passing a cutoff page that hasn't been created yet. */ SimpleLruTruncate(MultiXactOffsetCtl, MultiXactIdToOffsetPage(PreviousMultiXactId(newOldestMulti)));