From ea7af58c794321887f3492cc1b741b3614d15180 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 17 Sep 2026 18:13:54 +0300 Subject: [PATCH 1/1] Give a special error if multixid 1 is missing on pg_upgrade --- src/bin/pg_upgrade/multixact_read_v18.c | 21 ++++++- src/bin/pg_upgrade/multixact_read_v18.h | 2 +- src/bin/pg_upgrade/multixact_rewrite.c | 6 +- src/bin/pg_upgrade/slru_io.c | 74 +++++++++++++++++++------ src/bin/pg_upgrade/slru_io.h | 1 + 5 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/bin/pg_upgrade/multixact_read_v18.c b/src/bin/pg_upgrade/multixact_read_v18.c index c92b977530a..08fce9e7186 100644 --- a/src/bin/pg_upgrade/multixact_read_v18.c +++ b/src/bin/pg_upgrade/multixact_read_v18.c @@ -116,7 +116,8 @@ MXOffsetToFlagsBitShift(MultiXactOffset32 offset) * Returns the malloced memory used by the all other calls in this module. */ OldMultiXactReader * -AllocOldMultiXactRead(char *pgdata, MultiXactId nextMulti, +AllocOldMultiXactRead(char *pgdata, + MultiXactId oldestMulti, MultiXactId nextMulti, MultiXactOffset32 nextOffset) { OldMultiXactReader *state = pg_malloc_object(OldMultiXactReader); @@ -131,6 +132,24 @@ AllocOldMultiXactRead(char *pgdata, MultiXactId nextMulti, pg_sprintf(dir, "%s/pg_multixact/members", pgdata); state->members = AllocSlruRead(dir, false); + /* + * If oldestMulti is 1 (FirstMultiXactId), check that the corresponding + * offsets segment exists. You'd get an error later anyway when trying to + * read it, but we want to give a special error message for that case + * because there was a bug in old versions of pg_upgrade where oldestMulti + * was incorrectly set to 1. See commit a61daa14d5 that fixed that bug. + */ + if (oldestMulti == FirstMultiXactId && nextMulti != oldestMulti && + !SlruReadSegmentExists(state->offset, MultiXactIdToOffsetPage(oldestMulti))) + { + pg_log(PG_REPORT, "fatal"); + pg_fatal("Segment file containing multixid %u does not exist.\n" + "This can happen if an old version of pg_upgrade was used in the\n" + "past to upgrade the cluster to version 9.3. If that is the cause,\n" + "it can be fixed by running VACUUM FREEZE before upgrade.", + oldestMulti); + } + return state; } diff --git a/src/bin/pg_upgrade/multixact_read_v18.h b/src/bin/pg_upgrade/multixact_read_v18.h index 88b52b9d2e8..bc4b73d452c 100644 --- a/src/bin/pg_upgrade/multixact_read_v18.h +++ b/src/bin/pg_upgrade/multixact_read_v18.h @@ -27,7 +27,7 @@ typedef struct OldMultiXactReader } OldMultiXactReader; extern OldMultiXactReader *AllocOldMultiXactRead(char *pgdata, - MultiXactId nextMulti, + MultiXactId oldestMulti, MultiXactId nextMulti, MultiXactOffset32 nextOffset); extern bool GetOldMultiXactIdSingleMember(OldMultiXactReader *state, MultiXactId multi, diff --git a/src/bin/pg_upgrade/multixact_rewrite.c b/src/bin/pg_upgrade/multixact_rewrite.c index c7a1416494d..c670ff36fe0 100644 --- a/src/bin/pg_upgrade/multixact_rewrite.c +++ b/src/bin/pg_upgrade/multixact_rewrite.c @@ -62,7 +62,7 @@ rewrite_multixacts(MultiXactId from_multi, MultiXactId to_multi) * old cluster. */ old_reader = AllocOldMultiXactRead(old_cluster.pgdata, - old_cluster.controldata.chkpnt_nxtmulti, + from_multi, old_cluster.controldata.chkpnt_nxtmulti, old_cluster.controldata.chkpnt_nxtmxoff); for (MultiXactId multi = from_multi; multi != to_multi;) @@ -76,8 +76,8 @@ rewrite_multixacts(MultiXactId from_multi, MultiXactId to_multi) * Locking-only XIDs that may be part of multi-xids don't matter after * upgrade, as there can be no transactions running across upgrade. So * as a small optimization, we only read one member from each - * multixid: the one updating one, or if there was no update, - * arbitrarily the first locking xid. + * multixid: the updating one, or if there was no update, arbitrarily + * the first locking xid. */ multixid_valid = GetOldMultiXactIdSingleMember(old_reader, multi, &member); diff --git a/src/bin/pg_upgrade/slru_io.c b/src/bin/pg_upgrade/slru_io.c index aa9d59a0d7b..caa1a1859b2 100644 --- a/src/bin/pg_upgrade/slru_io.c +++ b/src/bin/pg_upgrade/slru_io.c @@ -20,6 +20,8 @@ static SlruSegState *AllocSlruSegState(const char *dir); static char *SlruFileName(SlruSegState *state, int64 segno); +static bool SlruReadSwitchSegment(SlruSegState *state, int64 segno, bool missing_ok); +static void SlruReadCloseSegment(SlruSegState *state); static void SlruFlush(SlruSegState *state); /* common parts of AllocSlruRead and AllocSlruWrite */ @@ -69,6 +71,49 @@ AllocSlruRead(const char *dir, bool long_segment_names) return state; } +/* + * Open the given segment, closing old one first if required. + * + * If 'missing_ok' is true and the file does not exist, returns false. + * Otherwise a missing file is fatal. + */ +static bool +SlruReadSwitchSegment(SlruSegState *state, int64 segno, bool missing_ok) +{ + SlruReadCloseSegment(state); + + state->fn = SlruFileName(state, segno); + if ((state->fd = open(state->fn, O_RDONLY | PG_BINARY, 0)) < 0) + { + if (missing_ok && errno == ENOENT) + { + pg_free(state->fn); + return false; + } + pg_fatal("could not open file \"%s\": %m", state->fn); + } + state->segno = segno; + return true; +} + +/* + * Close the current segment file, if any. + */ +static void +SlruReadCloseSegment(SlruSegState *state) +{ + if (state->segno != -1) + { + close(state->fd); + state->fd = -1; + + pg_free(state->fn); + state->fn = NULL; + + state->segno = -1; + } +} + /* * Read the given page into memory buffer. * @@ -96,23 +141,7 @@ SlruReadSwitchPageSlow(SlruSegState *state, uint64 pageno) /* If the new page is on a different SLRU segment, open the new segment */ segno = pageno / SLRU_PAGES_PER_SEGMENT; if (segno != state->segno) - { - if (state->segno != -1) - { - close(state->fd); - state->fd = -1; - - pg_free(state->fn); - state->fn = NULL; - - state->segno = -1; - } - - state->fn = SlruFileName(state, segno); - if ((state->fd = open(state->fn, O_RDONLY | PG_BINARY, 0)) < 0) - pg_fatal("could not open file \"%s\": %m", state->fn); - state->segno = segno; - } + SlruReadSwitchSegment(state, segno, false); offset = (pageno % SLRU_PAGES_PER_SEGMENT) * BLCKSZ; bytes_read = 0; @@ -146,6 +175,17 @@ SlruReadSwitchPageSlow(SlruSegState *state, uint64 pageno) return state->buf.data; } +/* + * Return true if the segment file containing given page exists. + */ +bool +SlruReadSegmentExists(SlruSegState *state, uint64 pageno) +{ + int64 segno = pageno / SLRU_PAGES_PER_SEGMENT; + + return SlruReadSwitchSegment(state, segno, true); +} + /* * Free the reader. */ diff --git a/src/bin/pg_upgrade/slru_io.h b/src/bin/pg_upgrade/slru_io.h index 0be83b28615..de871e37641 100644 --- a/src/bin/pg_upgrade/slru_io.h +++ b/src/bin/pg_upgrade/slru_io.h @@ -26,6 +26,7 @@ typedef struct SlruSegState } SlruSegState; extern SlruSegState *AllocSlruRead(const char *dir, bool long_segment_names); +extern bool SlruReadSegmentExists(SlruSegState *state, uint64 pageno); extern char *SlruReadSwitchPageSlow(SlruSegState *state, uint64 pageno); extern void FreeSlruRead(SlruSegState *state); -- 2.47.3