diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index bb436734585..db1cb8648ab 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -9143,22 +9162,31 @@ EvictExtraBuffers(int targetNBuffers, int currentNBuffers) buf_state = pg_atomic_read_u64(&desc->state); /* - * Nobody is expected to allocate new buffers while resizing is going - * on hence unlocked precheck should be safe and saves some cycles. + * A buffer whose tag is not published carries no hash-table mapping + * that shrinking has to clean up. BM_VALID is not sufficient here: + * a buffer mid-IO has BM_TAG_VALID but not yet BM_VALID, and skipping + * it would leave an orphaned mapping in the shrunk pool. */ - if (!(buf_state & BM_VALID)) + if (!(buf_state & BM_TAG_VALID)) continue; ResourceOwnerEnlarge(CurrentResourceOwner); ReservePrivateRefCountEntry(); - LockBufHdr(desc); + buf_state = LockBufHdr(desc); /* - * Now that we have locked buffer descriptor, make sure that the - * buffer without valid data has been skipped above. + * The unlocked precheck above is only a hint: a concurrent + * InvalidateBuffer() (e.g. DROP TABLE, TRUNCATE, DROP DATABASE) can + * clear BM_TAG_VALID between the precheck and this lock without + * needing our pin. Recheck under the lock and skip if so; there is + * nothing left for us to evict. */ - Assert(buf_state & BM_VALID); + if (!(buf_state & BM_TAG_VALID)) + { + UnlockBufHdr(desc); + continue; + } if (!EvictUnpinnedBufferInternal(desc, &buffer_flushed)) {