summaryrefslogtreecommitdiff
path: root/src/backend/storage/smgr/md.c
diff options
context:
space:
mode:
authorThomas Munro <tmunro@postgresql.org>2024-12-20 21:53:25 +1300
committerThomas Munro <tmunro@postgresql.org>2024-12-20 23:57:34 +1300
commitfb540b6aa5abe68d7ba1d84a90f5afd9c78564e3 (patch)
treee73b6e3f375b984c5c316f68cf236d22d703c0eb /src/backend/storage/smgr/md.c
parentc1c9df3159cfa91416bebe56ae50bc32d8a4e10b (diff)
Fix corruption when relation truncation fails.
RelationTruncate() does three things, while holding an AccessExclusiveLock and preventing checkpoints: 1. Logs the truncation. 2. Drops buffers, even if they're dirty. 3. Truncates some number of files. Step 2 could previously be canceled if it had to wait for I/O, and step 3 could and still can fail in file APIs. All orderings of these operations have data corruption hazards if interrupted, so we can't give up until the whole operation is done. When dirty pages were discarded but the corresponding blocks were left on disk due to ERROR, old page versions could come back from disk, reviving deleted data (see pgsql-bugs #18146 and several like it). When primary and standby were allowed to disagree on relation size, standbys could panic (see pgsql-bugs #18426) or revive data unknown to visibility management on the primary (theorized). Changes: * WAL is now unconditionally flushed first * smgrtruncate() is now called in a critical section, preventing interrupts and causing PANIC on file API failure * smgrtruncate() has a new parameter for existing fork sizes, because it can't call smgrnblocks() itself inside a critical section The changes apply to RelationTruncate(), smgr_redo() and pg_truncate_visibility_map(). That last is also brought up to date with other evolutions of the truncation protocol. The VACUUM FileTruncate() failure mode had been discussed in older reports than the ones referenced below, with independent analysis from many people, but earlier theories on how to fix it were too complicated to back-patch. The more recently invented cancellation bug was diagnosed by Alexander Lakhin. Other corruption scenarios were spotted by me while iterating on this patch and earlier commit 75818b3a. Back-patch to all supported releases. Reviewed-by: Michael Paquier <michael@paquier.xyz> Reviewed-by: Robert Haas <robertmhaas@gmail.com> Reported-by: rootcause000@gmail.com Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/18146-04e908c662113ad5%40postgresql.org Discussion: https://postgr.es/m/18426-2d18da6586f152d6%40postgresql.org
Diffstat (limited to 'src/backend/storage/smgr/md.c')
-rw-r--r--src/backend/storage/smgr/md.c28
1 files changed, 20 insertions, 8 deletions
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index f14c48da6cf..aa9ec9cf4d7 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -856,19 +856,21 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum)
/*
* mdtruncate() -- Truncate relation to specified number of blocks.
+ *
+ * Guaranteed not to allocate memory, so it can be used in a critical section.
+ * Caller must have called smgrnblocks() to obtain curnblk while holding a
+ * sufficient lock to prevent a change in relation size, and not used any smgr
+ * functions for this relation or handled interrupts in between. This makes
+ * sure we have opened all active segments, so that truncate loop will get
+ * them all!
*/
void
-mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
+mdtruncate(SMgrRelation reln, ForkNumber forknum,
+ BlockNumber curnblk, BlockNumber nblocks)
{
- BlockNumber curnblk;
BlockNumber priorblocks;
int curopensegs;
- /*
- * NOTE: mdnblocks makes sure we have opened all active segments, so that
- * truncation loop will get them all!
- */
- curnblk = mdnblocks(reln, forknum);
if (nblocks > curnblk)
{
/* Bogus request ... but no complaint if InRecovery */
@@ -1137,7 +1139,7 @@ _fdvec_resize(SMgrRelation reln,
reln->md_seg_fds[forknum] =
MemoryContextAlloc(MdCxt, sizeof(MdfdVec) * nseg);
}
- else
+ else if (nseg > reln->md_num_open_segs[forknum])
{
/*
* It doesn't seem worthwhile complicating the code to amortize
@@ -1149,6 +1151,16 @@ _fdvec_resize(SMgrRelation reln,
repalloc(reln->md_seg_fds[forknum],
sizeof(MdfdVec) * nseg);
}
+ else
+ {
+ /*
+ * We don't reallocate a smaller array, because we want mdtruncate()
+ * to be able to promise that it won't allocate memory, so that it is
+ * allowed in a critical section. This means that a bit of space in
+ * the array is now wasted, until the next time we add a segment and
+ * reallocate.
+ */
+ }
reln->md_num_open_segs[forknum] = nseg;
}