From 5eb4efbbe9155417755a635e44fcfda81ecb6cd2 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 2 Apr 2022 17:05:39 +1300 Subject: [PATCH v3 1/4] Rethink PROCSIGNAL_BARRIER_SMGRRELEASE. With sufficiently bad luck, it was possible for IssuePendingWritebacks() to reopen relation files just after we'd processed PROCSIGNAL_BARRIER_SMGRRELEASE but before the file was unlinked by some other backend. While commit 4eb21763 initially appeared to have fixed the spate of test failures we'd seen on Windows, Andres hammered it with some extra instrumentation to look out for writes to unlinked files and found this race. Defend against that by closing md.c's segments, instead of closing the fd.c's descriptors, and then teaching smgrwriteback() not to open files that aren't already open. This means that if PROCSIGNAL_BARRIER_SMGRRELEASE comes between smgrwrite() and smgrwriteback(), the writeback is silently skipped, but we don't risk caching a descriptor to a file that is unlinked. Reported-by: Andres Freund Discussion: https://postgr.es/m/20220209220004.kb3dgtn2x2k2gtdm%40alap3.anarazel.de --- src/backend/storage/smgr/md.c | 22 +++++++++------- src/backend/storage/smgr/smgr.c | 45 +++++++++++++++++++++++++++------ src/include/storage/md.h | 1 - src/include/storage/smgr.h | 2 ++ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 286dd3f755..41fa73087b 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -116,6 +116,8 @@ static MemoryContext MdCxt; /* context for all MdfdVec objects */ * mdnblocks(). */ #define EXTENSION_DONT_CHECK_SIZE (1 << 4) +/* don't try to open a segment, if not already open */ +#define EXTENSION_DONT_OPEN (1 << 5) /* local routines */ @@ -551,12 +553,6 @@ mdclose(SMgrRelation reln, ForkNumber forknum) } } -void -mdrelease(void) -{ - closeAllVfds(); -} - /* * mdprefetch() -- Initiate asynchronous read of the specified block of a relation */ @@ -605,11 +601,14 @@ mdwriteback(SMgrRelation reln, ForkNumber forknum, segnum_end; v = _mdfd_getseg(reln, forknum, blocknum, true /* not used */ , - EXTENSION_RETURN_NULL); + EXTENSION_DONT_OPEN); /* * We might be flushing buffers of already removed relations, that's - * ok, just ignore that case. + * ok, just ignore that case. If the segment file wasn't open already + * (ie from a recent mdwrite()), then we don't want to re-open it, to + * avoid a race with PROCSIGNAL_BARRIER_SMGRRELEASE that might leave us + * with a descriptor to a file that is about to be unlinked. */ if (!v) return; @@ -1202,7 +1201,8 @@ _mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno, /* some way to handle non-existent segments needs to be specified */ Assert(behavior & - (EXTENSION_FAIL | EXTENSION_CREATE | EXTENSION_RETURN_NULL)); + (EXTENSION_FAIL | EXTENSION_CREATE | EXTENSION_RETURN_NULL | + EXTENSION_DONT_OPEN)); targetseg = blkno / ((BlockNumber) RELSEG_SIZE); @@ -1213,6 +1213,10 @@ _mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno, return v; } + /* The caller only wants the segment if we already had it open. */ + if (behavior & EXTENSION_DONT_OPEN) + return NULL; + /* * The target segment is not yet open. Iterate over all the segments * between the last opened and the target segment. This way missing diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 2c7a2b2857..9cbfaa86cb 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -41,7 +41,6 @@ typedef struct f_smgr { void (*smgr_init) (void); /* may be NULL */ void (*smgr_shutdown) (void); /* may be NULL */ - void (*smgr_release) (void); /* may be NULL */ void (*smgr_open) (SMgrRelation reln); void (*smgr_close) (SMgrRelation reln, ForkNumber forknum); void (*smgr_create) (SMgrRelation reln, ForkNumber forknum, @@ -70,7 +69,6 @@ static const f_smgr smgrsw[] = { { .smgr_init = mdinit, .smgr_shutdown = NULL, - .smgr_release = mdrelease, .smgr_open = mdopen, .smgr_close = mdclose, .smgr_create = mdcreate, @@ -281,6 +279,42 @@ smgrclose(SMgrRelation reln) *owner = NULL; } +/* + * smgrrelease() -- Release all resources used by this object. + * + * The object remains valid. + */ +void +smgrrelease(SMgrRelation reln) +{ + for (int i = 0; i <= MAX_FORKNUM; ++i) + { + smgrsw[reln->smgr_which].smgr_close(reln, i); + reln->smgr_cached_nblocks[i] = InvalidBlockNumber; + } +} + +/* + * smgrreleaseall() -- Release resources used by all objects. + * + * This is called for PROCSIGNAL_BARRIER_SMGRRELEASE. + */ +void +smgrreleaseall(void) +{ + HASH_SEQ_STATUS status; + SMgrRelation reln; + + /* Nothing to do if hashtable not set up */ + if (SMgrRelationHash == NULL) + return; + + hash_seq_init(&status, SMgrRelationHash); + + while ((reln = (SMgrRelation) hash_seq_search(&status)) != NULL) + smgrrelease(reln); +} + /* * smgrcloseall() -- Close all existing SMgrRelation objects. */ @@ -698,11 +732,6 @@ AtEOXact_SMgr(void) bool ProcessBarrierSmgrRelease(void) { - for (int i = 0; i < NSmgr; i++) - { - if (smgrsw[i].smgr_release) - smgrsw[i].smgr_release(); - } - + smgrreleaseall(); return true; } diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 6e46d8d96a..ffffa40db7 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -23,7 +23,6 @@ extern void mdinit(void); extern void mdopen(SMgrRelation reln); extern void mdclose(SMgrRelation reln, ForkNumber forknum); -extern void mdrelease(void); extern void mdcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo); extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileNodeBackend rnode, ForkNumber forknum, bool isRedo); diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 8e3ef92cda..6b63c60fbd 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -85,6 +85,8 @@ extern void smgrclearowner(SMgrRelation *owner, SMgrRelation reln); extern void smgrclose(SMgrRelation reln); extern void smgrcloseall(void); extern void smgrclosenode(RelFileNodeBackend rnode); +extern void smgrrelease(SMgrRelation reln); +extern void smgrreleaseall(void); extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo); extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); -- 2.35.1