pg.ddx.io  pgsql-bugs@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Michael Paquier <michael@paquier.xyz>
To: Thomas Munro <thomas.munro@gmail.com>
Cc: Noah Misch <noah@leadboat.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Cc: exclusion@gmail.com
Cc: pgsql-bugs@lists.postgresql.org
Subject: Re: BUG #17928: Standby fails to decode WAL on termination of primary
Date: Mon, 21 Aug 2023 08:32:39 +0900
Message-ID: <ZOKil9ifJmmAb6di@paquier.xyz> (raw)
In-Reply-To: <ZN75xA4v14ImLvhy@paquier.xyz>
References: <CA+hUKGLOgGh=9siEhACUsrZEAQPVEQWgn35cggeci0_cNbPSSw@mail.gmail.com>
	<ZNrdQSWe1flOfHN3@paquier.xyz>
	<CA+hUKGLzv=8g3MFwm+TGFu+7koypgmU7qKHni+nrEnuJ8P6eog@mail.gmail.com>
	<ZNrqTmKGnZwz9gPI@paquier.xyz>
	<ZNsXBFsFsKcCbP0q@paquier.xyz>
	<CA+hUKGLhFdd-G1DCk9Ze3KnQ_2jUkxyXvGwV_Ha=DWMrDnGHng@mail.gmail.com>
	<ZNxqFgo243DOar4L@paquier.xyz>
	<ZN65RHuB+qNS/G9Q@paquier.xyz>
	<CA+hUKGL0P6CiKuTuxp_bV3peyE61huwPmLEFqVdCt5waqy3QOA@mail.gmail.com>
	<ZN75xA4v14ImLvhy@paquier.xyz>

On Fri, Aug 18, 2023 at 01:55:32PM +0900, Michael Paquier wrote:
> A new perl routine able to do a pg_control --includedir that scans a
> defined header with a regexp would be able to make the job for the
> constants.  If you're interested, I can code that up.

Please find attached a patch, that applies on top of the TAP test,
to implement a logic able to extract from header files the values
wanted.  This is Utils::scan_header(), for example:
my @scan_result = scan_header('server/access/xlog_internal.h',
    '#define\s+XLOG_PAGE_MAGIC\s+(\w+)');
my $XLP_PAGE_MAGIC = hex($scan_result[0]); 

This uses group capture so as it is possible to get more than one
field.  Guessing the size of the C structure is possible, but I've
found these options to be a bit awkward implementation-wise:
- Config would not help for the Postgres-specific declarations, like
TimeLineId, etc.
- I saw something that was specific to GCC..  cannot put my finger on
it now.

Another possibility would be to store the size in a different variable
in xlog_internal.h, coupled with a StaticAssertDecl() to make sure
that it matches with the real size.  While it has the same maintenance
cost as RECORD_HEADER_SIZE in the test itself, that's catching a
mismatching size at compilation-time rather than at test-time.

I am not sure that I'll be able to do more on this topic this week, at
least that's some progress.
--
Michael

Attachments: download all (4 files, .tar.gz)

v2-0001-Add-TAP-tests-for-end-of-WAL-conditions.patch (text/x-diff, 12.8K) download From b71a2aab7c56e99a4d3b6c713264b3c20a1a3499 Mon Sep 17 00:00:00 2001 From: Thomas Munro <thomas.munro@gmail.com> Date: Tue, 15 Aug 2023 17:37:53 +1200 Subject: [PATCH v2 1/3] Add TAP tests for end-of-WAL conditions. XXX --- src/test/recovery/meson.build | 1 + src/test/recovery/t/038_end_of_wal.pl | 415 ++++++++++++++++++++++++++ 2 files changed, 416 insertions(+) create mode 100644 src/test/recovery/t/038_end_of_wal.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index e7328e4894..10407668d4 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -43,6 +43,7 @@ tests += { 't/035_standby_logical_decoding.pl', 't/036_truncated_dropped.pl', 't/037_invalid_database.pl', + 't/038_end_of_wal.pl', ], }, } diff --git a/src/test/recovery/t/038_end_of_wal.pl b/src/test/recovery/t/038_end_of_wal.pl new file mode 100644 index 0000000000..fefb5b6034 --- /dev/null +++ b/src/test/recovery/t/038_end_of_wal.pl @@ -0,0 +1,415 @@ +# Copyright (c) 2023, PostgreSQL Global Development Group +# +# Test ways of detecting end of WAL. + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use Fcntl qw(SEEK_SET); + +use integer; # causes / operator to use integer math + +# Values that could be extracted from the source tree +my $RECORD_HEADER_SIZE = 24; +my $XLP_PAGE_MAGIC = 0xd113; +my $XLP_FIRST_IS_CONTRECORD = 0x1; + +# Values queried from the server +my $WAL_SEGMENT_SIZE; +my $WAL_BLOCK_SIZE; + +sub wal_segment_path +{ + my $node = shift; + my $tli = shift; + my $segment = shift; + my $wal_path = sprintf("%s/pg_wal/%08X%08X%08X", + $node->data_dir, + $tli, + 0, + $segment); + return $wal_path; +} + +sub lsn_to_segment_and_offset +{ + my $lsn = shift; + return ($lsn / $WAL_SEGMENT_SIZE, $lsn % $WAL_SEGMENT_SIZE); +} + +sub get_log_size +{ + my $node = shift; + return (stat $node->logfile)[7]; +} + +sub write_wal +{ + my $node = shift; + my $tli = shift; + my $lsn = shift; + my $data = shift; + + my ($segment, $offset) = lsn_to_segment_and_offset($lsn); + my $path = wal_segment_path($node, $tli, $segment); + + open my $fh, "+<:raw", $path or die; + seek($fh, $offset, SEEK_SET) or die; + print $fh $data; + close $fh; +} + +sub emit_message +{ + my $node = shift; + my $size = shift; + return int($node->safe_psql('postgres', "select pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'")); +} + +sub get_insert_lsn +{ + my $node = shift; + return int($node->safe_psql('postgres', "select pg_current_wal_insert_lsn() - '0/0'")); +} + +sub get_int_setting +{ + my $node = shift; + my $name = shift; + return int($node->safe_psql('postgres', "select setting from pg_settings where name = '$name'")); +} + +sub format_lsn +{ + my $lsn = shift; + return sprintf("%X/%X", $lsn >> 32, $lsn & 0xffffffff); +} + +sub start_of_page +{ + my $lsn = shift; + return $lsn & ~($WAL_BLOCK_SIZE - 1); +} + +sub start_of_next_page +{ + my $lsn = shift; + return start_of_page($lsn) + $WAL_BLOCK_SIZE; +} + +sub record_header +{ + my $xl_tot_len = shift; + my $xl_xid = shift || 0; + my $xl_prev = shift || 0; + my $xl_info = shift || 0; + my $xl_rmid = shift || 0; + my $xl_crc = shift || 0; + + # struct XLogRecord + return pack("IIQCCBBI", + $xl_tot_len, + $xl_xid, + $xl_prev, + $xl_info, + $xl_rmid, + 0, + 0, + $xl_crc); +} + +sub page_header +{ + my $xlp_magic = shift; + my $xlp_info = shift || 0; + my $xlp_tli = shift || 0; + my $xlp_pageaddr = shift || 0; + my $xlp_rem_len = shift || 0; + + # struct XLogPageHeaderData + return pack("SSIQI", + $xlp_magic, + $xlp_info, + $xlp_tli, + $xlp_pageaddr, + $xlp_rem_len); +} + +# Make sure we are far away enough from the end of a page that we could insert +# a couple of small records. +sub advance_out_of_record_splitting_zone +{ + my $node = shift; + + my $end_lsn = get_insert_lsn($node); + my $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + while ($page_offset >= $WAL_BLOCK_SIZE - 2000) + { + $end_lsn = emit_message($node, 2000); + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + } + return $end_lsn; +} + +# Advance so close to the end of a page that an XLogRecordHeader wouldn't fit. +sub advance_to_record_splitting_zone +{ + my $node = shift; + + my $end_lsn = get_insert_lsn($node); + my $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + + # get fairly close to the end of a page in big steps + while ($page_offset <= $WAL_BLOCK_SIZE - 512) + { + $end_lsn = emit_message($node, $WAL_BLOCK_SIZE - $page_offset - 256); + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + } + + # calibrate our message size so that we can get closer 8 bytes at + # a time (XXX something better is surely possible) + my $message_size = $WAL_BLOCK_SIZE - 80; + while ($page_offset <= $WAL_BLOCK_SIZE - $RECORD_HEADER_SIZE) + { + $end_lsn = emit_message($node, $message_size); + + my $old_offset = $page_offset; + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + + # adjust the message size until it causes 8 bytes changes in + # offset + my $delta = $page_offset - $old_offset; + if ($delta > 8) + { + $message_size -= 8; + } + elsif ($delta <= 0) + { + $message_size += 8; + } + } + return $end_lsn; +} + + +my $node = PostgreSQL::Test::Cluster->new("node"); +$node->init; +$node->append_conf('postgresql.conf', + q[wal_level = minimal + autovacuum = off + checkpoint_timeout = '30min' +]); +$node->start; +$node->safe_psql('postgres', "create table t as select 42"); + +$WAL_SEGMENT_SIZE = get_int_setting($node, 'wal_segment_size'); +$WAL_BLOCK_SIZE = get_int_setting($node, 'wal_block_size'); + + +my $end_lsn; +my $prev_lsn; +my $log_size; + +########################################################################### +note "Single-page end-of-WAL detection"; +########################################################################### + +# xl_tot_len is 0 (a common case, we hit trailing zeroes) +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid record length at .*: expected at least 24, got 0", + $log_size), + "xl_tot_len zero"); + +# xl_tot_len is < 24 (presumably recycled garbage) +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(23)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid record length at .*: expected at least 24, got 23", + $log_size), + "xl_tot_len short"); + +# need more pages, but xl_prev check fails first +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + 0xdeadbeef)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("record with incorrect prev-link 0/DEADBEEF at .*", + $log_size), + "xl_prev bad"); + +# xl_crc check fails +advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 10); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn - 8, '!'); # corrupt a byte in that record +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("incorrect resource manager data checksum in record at .*", + $log_size), + "xl_crc bad"); + + +########################################################################### +note "Multi-page end-of-WAL detection, header is not split"; +########################################################################### + +# good xl_prev, we hit zero page next (zero magic) +$prev_lsn = advance_out_of_record_splitting_zone($node); +#my $prev_lsn = emit_message($node, 0); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + $prev_lsn)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid magic number 0000 .* LSN .*", $log_size), + "xlp_magic zero"); + +# good xl_prev, we hit garbage page next (bad magic) +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + $prev_lsn)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header(0xcafe, + 0, + 1, + 0)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid magic number CAFE .* LSN .*", $log_size), + "xlp_magic bad"); + +# good xl_prev, we hit typical recycled page (good xlp_magic, bad xlp_pageaddr) +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + $prev_lsn)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + 0, + 1, + 0xbaaaaaad)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("unexpected pageaddr 0/BAAAAAAD in .*, LSN .*,", $log_size), + "xlp_pageaddr bad"); + +# good xl_prev, xlp_magic, xlp_pageaddr, but bogus xlp_info +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 42, + $prev_lsn)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + 0x1234, + 1, + start_of_next_page($end_lsn))); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid info bits 1234 in .*, LSN .*,", $log_size), + "xlp_info bad"); + +# good xl_prev, xlp_magic, xlp_pageaddr, but xlp_info doesn't mention cont record +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 42, + $prev_lsn)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + 0, + 1, + start_of_next_page($end_lsn))); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("there is no contrecord flag at .*", $log_size), + "xlp_info lacks XLP_FIRST_IS_CONTRECORD"); + +# good xl_prev, xlp_magic, xlp_pageaddr, xlp_info but xlp_rem_len doesn't add up +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 42, + $prev_lsn)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + $XLP_FIRST_IS_CONTRECORD, + 1, + start_of_next_page($end_lsn), + 123456)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid contrecord length 123456 .* at .*", $log_size), + "xlp_rem_len bad"); + + +########################################################################### +note "Multi-page, but header is split, so page checks are done first"; +########################################################################### + +# xlp_prev is bad and xl_tot_len is too big, but we'll check xlp_magic first +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + 0xdeadbeef)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid magic number 0000 .* LSN .*", $log_size), + "xlp_magic zero (split record header)"); + +# and we'll also check xlp_pageaddr before any header checks +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + 0xdeadbeef)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + $XLP_FIRST_IS_CONTRECORD, + 1, + 0xbaaaaaad)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("unexpected pageaddr 0/BAAAAAAD in .*, LSN .*,", $log_size), + "xlp_pageaddr bad (split record header)"); + +# we'll also discover that xlp_rem_len doesn't add up before any +# header checks +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, 1, $end_lsn, record_header(2 * 1024 * 1024 * 1024, + 0, + 0xdeadbeef)); +write_wal($node, 1, start_of_next_page($end_lsn), + page_header($XLP_PAGE_MAGIC, + $XLP_FIRST_IS_CONTRECORD, + 1, + start_of_next_page($end_lsn), + 123456)); +$log_size = get_log_size($node); +$node->start; +ok($node->log_contains("invalid contrecord length 123456 .* at .*", $log_size), + "xlp_rem_len bad (split record header)"); + +done_testing(); -- 2.40.1
v2-0002-Don-t-trust-unvalidated-xl_tot_len.patch (text/x-diff, 7.8K) download From 7b3dc9aa37c571091dcc73e3e30567803bffb5f1 Mon Sep 17 00:00:00 2001 From: Thomas Munro <thomas.munro@gmail.com> Date: Mon, 14 Aug 2023 17:06:42 +1200 Subject: [PATCH v2 2/3] Don't trust unvalidated xl_tot_len. xl_tot_len comes first in a WAL record. Usually we don't trust it to be the true length until we've validated the record header. If the record header was split across two pages, previously we wouldn't do the validation until after we'd already tried to allocate enough memory to hold the record, which was bad because it might actually be garbage bytes from a recycled WAL file, so we could try to allocate a lot of memory. Release 15 made it worse. Since 70b4f82a4b5, we'd at least generate an end-of-WAL condition if the garbage 4 byte value happened to be > 1GB, but we'd still try to allocate up to 1GB of memory bogusly otherwise. That was an improvement, but unfortunately release 15 tries to allocate another object before that, so you could get a FATAL error and recovery could fail. We can fix both variants of the problem more fundamentally in old and new branches using pre-existing page-level validation. Whenever a record header spans a page boundary, we just need to defer all memory allocation based on xl_tot_len until we've read the following page. At that point we know that its first sector is not recycled data, by checking its xlp_pageaddr, and that its xlp_rem_len agrees with xl_tot_len on the preceding page. That is strong evidence that xl_tot_len was truly the start of a record that was logged. This problem was most likely to occur on a standby, because walreceiver.c recycles WAL files without zeroing out trailing regions of each page. We should fix that too, but that wouldn't protect us from rare crash scenarios where the trailing zeroes don't make it to disk. Back-patch to 12. This failure mode is rare, and 11 is about to go out of support, so we decided not to change its final release. Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/17928-aa92416a70ff44a2%40postgresql.org --- src/backend/access/transam/xlogreader.c | 110 +++++++++++++----------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index c9f9f6e98f..d5ed139131 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -203,18 +203,6 @@ allocate_recordbuf(XLogReaderState *state, uint32 reclength) #ifndef FRONTEND - /* - * Note that in much unlucky circumstances, the random data read from a - * recycled segment can cause this routine to be called with a size - * causing a hard failure at allocation. For a standby, this would cause - * the instance to stop suddenly with a hard failure, preventing it to - * retry fetching WAL from one of its sources which could allow it to move - * on with replay without a manual restart. If the data comes from a past - * recycled segment and is still valid, then the allocation may succeed - * but record checks are going to fail so this would be short-lived. If - * the allocation fails because of a memory shortage, then this is not a - * hard failure either per the guarantee given by MCXT_ALLOC_NO_OOM. - */ if (!AllocSizeIsValid(newSize)) return false; @@ -669,41 +657,26 @@ restart: } else { - /* XXX: more validation should be done here */ - if (total_len < SizeOfXLogRecord) - { - report_invalid_record(state, - "invalid record length at %X/%X: expected at least %u, got %u", - LSN_FORMAT_ARGS(RecPtr), - (uint32) SizeOfXLogRecord, total_len); - goto err; - } + /* We'll validate the header once we have the next page. */ gotheader = false; } /* - * Find space to decode this record. Don't allow oversized allocation if - * the caller requested nonblocking. Otherwise, we *have* to try to - * decode the record now because the caller has nothing else to do, so - * allow an oversized record to be palloc'd if that turns out to be - * necessary. + * Try to find space to decode this record, if we can do so without + * calling palloc. If we can't, we'll try again below after we've + * validated that total_len isn't garbage bytes from a recycled WAL page. */ decoded = XLogReadRecordAlloc(state, total_len, - !nonblocking /* allow_oversized */ ); - if (decoded == NULL) + false /* allow_oversized */ ); + if (decoded == NULL && nonblocking) { /* - * There is no space in the decode buffer. The caller should help - * with that problem by consuming some records. + * There is no space in the circular decode buffer, and the caller is + * only reading ahead. The caller should consume existing records to + * make space. */ - if (nonblocking) - return XLREAD_WOULDBLOCK; - - /* We failed to allocate memory for an oversized record. */ - report_invalid_record(state, - "out of memory while trying to decode a record of length %u", total_len); - goto err; + return XLREAD_WOULDBLOCK; } len = XLOG_BLCKSZ - RecPtr % XLOG_BLCKSZ; @@ -718,16 +691,11 @@ restart: assembled = true; /* - * Enlarge readRecordBuf as needed. + * We always have space for a couple of pages, enough to validate a + * boundary-spanning record header. */ - if (total_len > state->readRecordBufSize && - !allocate_recordbuf(state, total_len)) - { - /* We treat this as a "bogus data" condition */ - report_invalid_record(state, "record length %u at %X/%X too long", - total_len, LSN_FORMAT_ARGS(RecPtr)); - goto err; - } + Assert(state->readRecordBufSize >= XLOG_BLCKSZ * 2); + Assert(state->readRecordBufSize >= len); /* Copy the first fragment of the record from the first page. */ memcpy(state->readRecordBuf, @@ -824,8 +792,35 @@ restart: goto err; gotheader = true; } - } while (gotlen < total_len); + /* + * We might need a bigger buffer. We have validated the record + * header, in the case that it split over a page boundary. We've + * also cross-checked total_len against xlp_rem_len on the second + * page, and verified xlp_pageaddr on both. + */ + if (total_len > state->readRecordBufSize) + { + char save_copy[XLOG_BLCKSZ * 2]; + + /* + * Save and restore the data we already had. It can't be more + * than two pages. + */ + Assert(gotlen <= lengthof(save_copy)); + Assert(gotlen <= state->readRecordBufSize); + memcpy(save_copy, state->readRecordBuf, gotlen); + if (!allocate_recordbuf(state, total_len)) + { + /* We treat this as a "bogus data" condition */ + report_invalid_record(state, "record length %u at %X/%X too long", + total_len, LSN_FORMAT_ARGS(RecPtr)); + goto err; + } + memcpy(state->readRecordBuf, save_copy, gotlen); + buffer = state->readRecordBuf + gotlen; + } + } while (gotlen < total_len); Assert(gotheader); record = (XLogRecord *) state->readRecordBuf; @@ -867,6 +862,25 @@ restart: state->NextRecPtr -= XLogSegmentOffset(state->NextRecPtr, state->segcxt.ws_segsize); } + /* + * If we got here without a DecodedXLogRecord, it means we needed to + * validate total_len before trusting it, but by now now we've done that. + */ + if (decoded == NULL) + { + Assert(!nonblocking); + decoded = XLogReadRecordAlloc(state, + total_len, + true /* allow_oversized */ ); + if (decoded == NULL) + { + /* We failed to allocate memory for an oversized record. */ + report_invalid_record(state, + "out of memory while trying to decode a record of length %u", total_len); + goto err; + } + } + if (DecodeXLogRecord(state, decoded, record, RecPtr, &errormsg)) { /* Record the location of the next record. */ @@ -895,8 +909,6 @@ restart: state->decode_queue_head = decoded; return XLREAD_SUCCESS; } - else - return XLREAD_FAIL; err: if (assembled) -- 2.40.1
v2-0003-Implement-routine-to-scan-and-extra-values-from-h.patch (text/x-diff, 2.7K) download From 87912b0d0dc272b20ec9659479eebdfc73d78c79 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@paquier.xyz> Date: Mon, 21 Aug 2023 08:17:32 +0900 Subject: [PATCH v2 3/3] Implement routine to scan and extra values from headers --- src/test/perl/PostgreSQL/Test/Utils.pm | 43 ++++++++++++++++++++++++++ src/test/recovery/t/038_end_of_wal.pl | 12 +++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index 617caa022f..80ea9b0759 100644 --- a/src/test/perl/PostgreSQL/Test/Utils.pm +++ b/src/test/perl/PostgreSQL/Test/Utils.pm @@ -71,6 +71,7 @@ our @EXPORT = qw( chmod_recursive check_pg_config dir_symlink + scan_header system_or_bail system_log run_log @@ -702,6 +703,48 @@ sub chmod_recursive =pod +=item scan_header(header_path, regexp) + +Returns an array that stores all the matches of the given regular expression +within the PostgreSQL installation's C<header_path>. This can be used to +retrieve specific value patterns from the installation's header files. + +=cut + +sub scan_header +{ + my ($header_path, $regexp) = @_; + + my ($stdout, $stderr); + my $result = IPC::Run::run [ 'pg_config', '--includedir' ], '>', + \$stdout, '2>', \$stderr + or die "could not execute pg_config"; + chomp($stdout); + $stdout =~ s/\r$//; + + open my $header_h, '<', "$stdout/$header_path" or die "$!"; + + my @match = undef; + while (<$header_h>) + { + my $line = $_; + + if ($line =~ /^$regexp/) + { + # Found match, so store all the results. + @match = @{^CAPTURE}; + last; + } + } + + close $header_h; + die "could not find match in header $header_path\n" + unless @match; + return @match; +} + +=pod + =item check_pg_config(regexp) Return the number of matches of the given regular expression diff --git a/src/test/recovery/t/038_end_of_wal.pl b/src/test/recovery/t/038_end_of_wal.pl index fefb5b6034..578026e692 100644 --- a/src/test/recovery/t/038_end_of_wal.pl +++ b/src/test/recovery/t/038_end_of_wal.pl @@ -11,10 +11,16 @@ use Fcntl qw(SEEK_SET); use integer; # causes / operator to use integer math -# Values that could be extracted from the source tree +# Header size of record header. my $RECORD_HEADER_SIZE = 24; -my $XLP_PAGE_MAGIC = 0xd113; -my $XLP_FIRST_IS_CONTRECORD = 0x1; + +# Fields retrieved from headers. +my @scan_result = scan_header('server/access/xlog_internal.h', + '#define\s+XLOG_PAGE_MAGIC\s+(\w+)'); +my $XLP_PAGE_MAGIC = hex($scan_result[0]); +@scan_result = scan_header('server/access/xlog_internal.h', + '#define\s+XLP_FIRST_IS_CONTRECORD\s+(\w+)'); +my $XLP_FIRST_IS_CONTRECORD = hex($scan_result[0]); # Values queried from the server my $WAL_SEGMENT_SIZE; -- 2.40.1
signature.asc (application/pgp-signature, 832B) download

view thread (86+ messages)  latest in thread

Message-ID: <ZOKil9ifJmmAb6di@paquier.xyz>
Permalink:  ../ZOKil9ifJmmAb6di@paquier.xyz/
Also on:    postgresql.org/message-id/ZOKil9ifJmmAb6di@paquier.xyz

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-bugs@postgresql.org
  Cc: michael@paquier.xyz, thomas.munro@gmail.com, noah@leadboat.com, tgl@sss.pgh.pa.us, horikyota.ntt@gmail.com, exclusion@gmail.com, pgsql-bugs@lists.postgresql.org
  Subject: Re: BUG #17928: Standby fails to decode WAL on termination of primary
  In-Reply-To: <ZOKil9ifJmmAb6di@paquier.xyz>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox