mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
pg_upgrade: Fix last multixid ending at offset wraparound
On v18 and below, if the newest multixid's members end exactly at the
the 32-bit offset wraparound point, the next offset in the control
file is 0. GetOldMultiXactIdSingleMember() treated that as an invalid
entry and dropped the multixid's members, so after upgrade accessing
the tuple failed with "MultiXact N has invalid offset".
To fix, only check for zero offset when it was read from the next
multixid's slot, like GetMultiXactIdMembers() did in v18. This is
unlikely to happen in practice, but better to get it right.
The bug came with commit bd8d9c9bdf in v19.
Author: Zsolt Parragi <zsolt.parragi@percona.com>
Discussion: https://www.postgresql.org/message-id/CAN4CZFM8BCazGz6s1Vn5tMEXYiXE2fi8T2rwmwXSty5gD+h9rA@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user