From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Palak Chaturvedi Date: Mon, 14 Sep 2026 15:33:02 +0000 Subject: [PATCH] buffermgr: validate buffer hints and rollback metadata - ReadRecentBuffer(): range-check the recent_buffer hint, which can go stale after a shrink - 001_resize_fault_tolerance.pl: verify shmem structure sizes are actually restored after a rolled-back resize, not just the NBuffers counter --- src/backend/access/transam/xlogutils.c | 2 + src/backend/storage/buffer/bufmgr.c | 16 +++++------ src/test/buffermgr/t/001_resize_fault_tolerance.pl | 30 +++++++++++++++++--- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 58b9dab6a90..ed5559b6624 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -491,8 +491,8 @@ XLogReadBufferExtended(RelFileLocator rlocator, ForkNumber forknum, Assert(blkno != P_NEW); /* Do we have a clue where the buffer might be already? */ - if (BufferIsValid(recent_buffer) && + if (recent_buffer != InvalidBuffer && mode == RBM_NORMAL && ReadRecentBuffer(rlocator, forknum, blkno, recent_buffer)) { diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index d51d1f72392..8cc5a6316a7 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -828,14 +828,9 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum) * successful. Return true if the buffer is valid and still has the expected * tag. In that case, the buffer is pinned and the usage count is bumped. * - * The callers of this function should make sure that the buffer is valid even - * if the shared buffer pool has undergone a resize. Buffer pool resizing waits - * for all backends to acknowledge the barrier before changing the buffer pool - * size. Hence the caller should call this function after validation without an - * intervening ProcSignalBarrier processing. - * - * TODO: This function could perform the validation in this function itself - * instead of relying on the two callers who do it currently. + * A shrink can leave recent_buffer outside the current pool. Reject such + * hints before accessing the descriptor. Do not process resize barriers + * between this check and acquiring the pin. */ bool ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockNum, @@ -845,7 +840,10 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN BufferTag tag; uint64 buf_state; - Assert(BufferIsValid(recent_buffer)); + if (recent_buffer == InvalidBuffer || + recent_buffer > NBuffers || + recent_buffer < -NLocBuffer) + return false; ResourceOwnerEnlarge(CurrentResourceOwner); ReservePrivateRefCountEntry(); diff --git a/src/test/buffermgr/t/001_resize_fault_tolerance.pl b/src/test/buffermgr/t/001_resize_fault_tolerance.pl index 87c95dbedf1..086fdc1b702 100644 --- a/src/test/buffermgr/t/001_resize_fault_tolerance.pl +++ b/src/test/buffermgr/t/001_resize_fault_tolerance.pl @@ -27,6 +27,7 @@ $node->append_conf('postgresql.conf', "shared_buffers = $initial_nbuffers"); $node->append_conf('postgresql.conf', 'max_shared_buffers = 32'); +$node->append_conf('postgresql.conf', 'huge_pages = off'); $node->append_conf('postgresql.conf', 'restart_after_crash = on'); $node->start; # Bail out if this build does not support resizable shared memory, which @@ -43,6 +44,21 @@ $node->safe_psql('postgres', "CREATE EXTENSION injection_points"); # Helper functions # ============================================================================= +# Snapshot the buffer manager's allocation metadata. +sub buffer_shmem_allocations +{ + my $allocations = $node->safe_psql('postgres', q{ + SELECT string_agg(name || '=' || allocated_size, ', ' ORDER BY name) + FROM pg_shmem_allocations + WHERE name IN ('Buffer Blocks', 'Buffer Descriptors', + 'Buffer IO Condition Variables', 'Checkpoint BufferIds', + 'Shared Buffer Lookup Table') + HAVING count(*) = 5 + }); + BAIL_OUT('missing buffer manager allocation metadata') if $allocations eq ''; + return $allocations; +} + # Setup resize operation to be interrupted. # # Prepare to resize the buffer pool to a target size. Start a resize session @@ -191,11 +204,13 @@ sub interrupt_resize_session # was used to interrupt the resize operation. # - orig_nbuffers and target_nbuffers: the original and target buffer sizes for # the resize operation. +# - orig_allocations: buffer_shmem_allocations() taken before the resize began. # - test_label: a label to create unique test names for different tests sub check_interrupted_resize { my ($sentinel_session, $resize_session, $log_offset, $mode, - $injection_point, $orig_nbuffers, $target_nbuffers, $test_label) = @_; + $injection_point, $orig_nbuffers, $target_nbuffers, $orig_allocations, + $test_label) = @_; my $resize_pid = $resize_session->{backend_pid}; my $sentinel_pid = $sentinel_session->{backend_pid}; @@ -269,7 +284,8 @@ sub check_interrupted_resize "$orig_nbuffers|$orig_nbuffers|$orig_nbuffers|0", "$test_label: buffer resize rolled back after $mode"); - # TODO: Also check that the pg_shmem_allocations values are not changed + is(buffer_shmem_allocations(), $orig_allocations, + "$test_label: shared memory allocations unchanged after $mode"); is($node->safe_psql('postgres', "SELECT setting FROM pg_settings WHERE name = 'shared_buffers'"), @@ -386,6 +402,7 @@ sub test_interrupt_resize_at_injection_point my $orig_nbuffers = $node->safe_psql('postgres', "SELECT current_nbuffers FROM pg_get_buffer_resize_status()"); + my $orig_allocations = buffer_shmem_allocations(); my $log_offset = -s $node->logfile; # Start a sentinel session that will be used to detect whether the @@ -400,7 +417,7 @@ sub test_interrupt_resize_at_injection_point check_interrupted_resize($sentinel_session, $resize_session, $log_offset, $mode, $injection_point, $orig_nbuffers, $target_nbuffers, - $test_label); + $orig_allocations, $test_label); } # Driver function: @@ -563,6 +580,7 @@ sub test_fault_resize_waiting_barrier my $orig_nbuffers = $node->safe_psql('postgres', "SELECT current_nbuffers FROM pg_get_buffer_resize_status()"); + my $orig_allocations = buffer_shmem_allocations(); my $log_offset = -s $node->logfile; my $peer_session = start_peer_session_with_injection_point($injection_point, 'wait'); @@ -587,7 +605,8 @@ sub test_fault_resize_waiting_barrier interrupt_resize_session($mode, $resize_session); check_interrupted_resize($sentinel_session, $resize_session, $log_offset, - $mode, $injection_point, $orig_nbuffers, $target_nbuffers, $test_label); + $mode, $injection_point, $orig_nbuffers, $target_nbuffers, + $orig_allocations, $test_label); # Cleanup peer session. If the postmaster restarted all backends, the peer # backend is already gone. -- 2.43.0