diff --git a/src/bin/pg_upgrade/multixact_read_v18.c b/src/bin/pg_upgrade/multixact_read_v18.c index c92b977530a..1a8dee29eb2 100644 --- a/src/bin/pg_upgrade/multixact_read_v18.c +++ b/src/bin/pg_upgrade/multixact_read_v18.c @@ -226,7 +226,11 @@ GetOldMultiXactIdSingleMember(OldMultiXactReader *state, MultiXactId multi, if (nextMXact == tmpMXact) { - /* Corner case 1: there is no next multixact */ + /* + * Corner case 1: there is no next multixact. nextOffset can be zero + * here, if the members of this multixact ended exactly at offset + * wraparound. The length calculation below copes with that. + */ nextMXOffset = nextOffset; } else @@ -246,13 +250,14 @@ GetOldMultiXactIdSingleMember(OldMultiXactReader *state, MultiXactId multi, offptr = (MultiXactOffset32 *) buf; offptr += entryno; nextMXOffset = *offptr; + + if (nextMXOffset == 0) + { + /* Invalid entry. These can be left behind on a server crash. */ + return false; + } } - if (nextMXOffset == 0) - { - /* Invalid entry. These can be left behind on a server crash. */ - return false; - } length = nextMXOffset - offset; if (length < 0) diff --git a/src/bin/pg_upgrade/t/007_multixact_conversion.pl b/src/bin/pg_upgrade/t/007_multixact_conversion.pl index 867a0623153..5c87c2527ee 100644 --- a/src/bin/pg_upgrade/t/007_multixact_conversion.pl +++ b/src/bin/pg_upgrade/t/007_multixact_conversion.pl @@ -440,4 +440,48 @@ SKIP: upgrade_and_compare($tag, $old, $new); } +# Offset wraparound at the last multixid: The members of the newest +# multixid end exactly at the 32-bit offset wraparound point, so that +# the old cluster's next offset is 0. +SKIP: +{ + skip + "skipping mxoffset conversion tests because upgrading from the old version does not require conversion" + if ($old_version >= '19devel'); + + my $tag = 'last_wraparound'; + my $old = + PostgreSQL::Test::Cluster->new("${tag}_oldnode", + install_path => $ENV{oldinstall}); + my $new = PostgreSQL::Test::Cluster->new("${tag}_newnode"); + + $old->init(extra => ['-k']); + + reset_mxid_mxoffset_pre_v19($old, 0xFFFFFA00, 0xFFFFFFFE); + + # Create one multixid with two members, a locker and an updater + $old->start; + $old->safe_psql( + 'postgres', qq[ + CREATE TABLE mxofftest (id INT PRIMARY KEY, n_updated INT) + WITH (AUTOVACUUM_ENABLED=FALSE); + INSERT INTO mxofftest SELECT G, 0 FROM GENERATE_SERIES(1, 2) G; + ]); + my $locker = + $new->background_psql('', connstr => $old->connstr('postgres')); + $locker->query_safe("BEGIN"); + $locker->query_safe("SELECT * FROM mxofftest WHERE id = 1 FOR KEY SHARE"); + $old->safe_psql('postgres', + "UPDATE mxofftest SET n_updated = n_updated + 1 WHERE id = 1"); + $locker->query_safe("COMMIT"); + $locker->quit; + $old->stop; + + my (undef, undef, $finish_mxoff) = read_multixid_fields($old); + is($finish_mxoff, 0, "mxoff wrapped around to 0 in old cluster"); + + $new->init; + upgrade_and_compare($tag, $old, $new); +} + done_testing();