From 473320e6c87cc7ffc704b2823c685b3dd13c0342 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 17 Oct 2012 12:38:33 -0400 Subject: [PATCH] Close un-owned SMgrRelations at transaction end. If an SMgrRelation is not "owned" by a relcache entry, don't allow it to live past transaction end. This design allows the same SMgrRelation to be used for blind writes of multiple blocks during a transaction, but ensures that we don't hold onto such an SMgrRelation indefinitely. Because an SMgrRelation typically corresponds to open file descriptors at the fd.c level, leaving it open when there's no corresponding relcache entry can mean that we prevent the kernel from reclaiming deleted disk space. (While CacheInvalidateSmgr messages usually fix that, there are cases where they're not issued, such as DROP DATABASE. We might want to add some more sinval messaging for that, but I'd be inclined to keep this type of logic anyway, since allowing VFDs to accumulate indefinitely for blind-written relations doesn't seem like a good idea.) This code replaces a previous attempt towards the same goal that proved to be unreliable. Back-patch to 9.1 where the previous patch was added. --- src/backend/access/transam/xact.c | 5 +- src/backend/postmaster/bgwriter.c | 1 + src/backend/postmaster/walwriter.c | 1 + src/backend/storage/smgr/smgr.c | 80 +++++++++++++++++++++++++++++- src/include/storage/smgr.h | 7 +++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index a38fed18fb..fb91d62ce8 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -1917,7 +1917,7 @@ CommitTransaction(void) AtEOXact_SPI(true); AtEOXact_on_commit_actions(true); AtEOXact_Namespace(true); - /* smgrcommit already done */ + AtEOXact_SMgr(); AtEOXact_Files(); AtEOXact_ComboCid(); AtEOXact_HashTables(true); @@ -2163,7 +2163,7 @@ PrepareTransaction(void) AtEOXact_SPI(true); AtEOXact_on_commit_actions(true); AtEOXact_Namespace(true); - /* smgrcommit already done */ + AtEOXact_SMgr(); AtEOXact_Files(); AtEOXact_ComboCid(); AtEOXact_HashTables(true); @@ -2309,6 +2309,7 @@ AbortTransaction(void) AtEOXact_SPI(false); AtEOXact_on_commit_actions(false); AtEOXact_Namespace(false); + AtEOXact_SMgr(); AtEOXact_Files(); AtEOXact_ComboCid(); AtEOXact_HashTables(false); diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index c850033537..83c13e252c 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -305,6 +305,7 @@ BackgroundWriterMain(void) false, true); /* we needn't bother with the other ResourceOwnerRelease phases */ AtEOXact_Buffers(false); + AtEOXact_SMgr(); AtEOXact_Files(); AtEOXact_HashTables(false); diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c index d0d7c9bebf..80991f6f56 100644 --- a/src/backend/postmaster/walwriter.c +++ b/src/backend/postmaster/walwriter.c @@ -177,6 +177,7 @@ WalWriterMain(void) false, true); /* we needn't bother with the other ResourceOwnerRelease phases */ AtEOXact_Buffers(false); + AtEOXact_SMgr(); AtEOXact_Files(); AtEOXact_HashTables(false); diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index a6610bf4f8..a950b85323 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -78,11 +78,15 @@ static const int NSmgr = lengthof(smgrsw); /* * Each backend has a hashtable that stores all extant SMgrRelation objects. + * In addition, "unowned" SMgrRelation objects are chained together in a list. */ static HTAB *SMgrRelationHash = NULL; +static SMgrRelation first_unowned_reln = NULL; + /* local function prototypes */ static void smgrshutdown(int code, Datum arg); +static void remove_from_unowned_list(SMgrRelation reln); /* @@ -126,7 +130,7 @@ smgrshutdown(int code, Datum arg) /* * smgropen() -- Return an SMgrRelation object, creating it if need be. * - * This does not attempt to actually open the object. + * This does not attempt to actually open the underlying file. */ SMgrRelation smgropen(RelFileNode rnode, BackendId backend) @@ -146,6 +150,7 @@ smgropen(RelFileNode rnode, BackendId backend) ctl.hash = tag_hash; SMgrRelationHash = hash_create("smgr relation table", 400, &ctl, HASH_ELEM | HASH_FUNCTION); + first_unowned_reln = NULL; } /* Look up or create an entry */ @@ -170,6 +175,10 @@ smgropen(RelFileNode rnode, BackendId backend) /* mark it not open */ for (forknum = 0; forknum <= MAX_FORKNUM; forknum++) reln->md_fd[forknum] = NULL; + + /* place it at head of unowned list (to make smgrsetowner cheap) */ + reln->next_unowned_reln = first_unowned_reln; + first_unowned_reln = reln; } return reln; @@ -184,20 +193,60 @@ smgropen(RelFileNode rnode, BackendId backend) void smgrsetowner(SMgrRelation *owner, SMgrRelation reln) { + /* We don't currently support "disowning" an SMgrRelation here */ + Assert(owner != NULL); + /* * First, unhook any old owner. (Normally there shouldn't be any, but it * seems possible that this can happen during swap_relation_files() * depending on the order of processing. It's ok to close the old * relcache entry early in that case.) + * + * If there isn't an old owner, then the reln should be in the unowned + * list, and we need to remove it. */ if (reln->smgr_owner) *(reln->smgr_owner) = NULL; + else + remove_from_unowned_list(reln); /* Now establish the ownership relationship. */ reln->smgr_owner = owner; *owner = reln; } +/* + * remove_from_unowned_list -- unlink an SMgrRelation from the unowned list + * + * If the reln is not present in the list, nothing happens. Typically this + * would be caller error, but there seems no reason to throw an error. + * + * In the worst case this could be rather slow; but in all the cases that seem + * likely to be performance-critical, the reln being sought will actually be + * first in the list. Furthermore, the number of unowned relns touched in any + * one transaction shouldn't be all that high typically. So it doesn't seem + * worth expending the additional space and management logic needed for a + * doubly-linked list. + */ +static void +remove_from_unowned_list(SMgrRelation reln) +{ + SMgrRelation *link; + SMgrRelation cur; + + for (link = &first_unowned_reln, cur = *link; + cur != NULL; + link = &cur->next_unowned_reln, cur = *link) + { + if (cur == reln) + { + *link = cur->next_unowned_reln; + cur->next_unowned_reln = NULL; + break; + } + } +} + /* * smgrexists() -- Does the underlying file for a fork exist? */ @@ -221,6 +270,9 @@ smgrclose(SMgrRelation reln) owner = reln->smgr_owner; + if (!owner) + remove_from_unowned_list(reln); + if (hash_search(SMgrRelationHash, (void *) &(reln->smgr_rnode), HASH_REMOVE, NULL) == NULL) @@ -546,3 +598,29 @@ smgrpostckpt(void) (*(smgrsw[i].smgr_post_ckpt)) (); } } + +/* + * AtEOXact_SMgr + * + * This routine is called during transaction commit or abort (it doesn't + * particularly care which). All transient SMgrRelation objects are closed. + * + * We do this as a compromise between wanting transient SMgrRelations to + * live awhile (to amortize the costs of blind writes of multiple blocks) + * and needing them to not live forever (since we're probably holding open + * a kernel file descriptor for the underlying file, and we need to ensure + * that gets closed reasonably soon if the file gets deleted). + */ +void +AtEOXact_SMgr(void) +{ + /* + * Zap all unowned SMgrRelations. We rely on smgrclose() to remove each + * one from the list. + */ + while (first_unowned_reln != NULL) + { + Assert(first_unowned_reln->smgr_owner == NULL); + smgrclose(first_unowned_reln); + } +} diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 47c4c1640a..9a135cb5a2 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -35,6 +35,9 @@ * without having to make the smgr explicitly aware of relcache. There * can't be more than one "owner" pointer per SMgrRelation, but that's * all we need. + * + * SMgrRelations that do not have an "owner" are considered to be transient, + * and are deleted at end of transaction. */ typedef struct SMgrRelationData { @@ -65,6 +68,9 @@ typedef struct SMgrRelationData /* for md.c; NULL for forks that are not open */ struct _MdfdVec *md_fd[MAX_FORKNUM + 1]; + + /* if unowned, list link in list of all unowned SMgrRelations */ + struct SMgrRelationData *next_unowned_reln; } SMgrRelationData; typedef SMgrRelationData *SMgrRelation; @@ -97,6 +103,7 @@ extern void smgrimmedsync(SMgrRelation reln, ForkNumber forknum); extern void smgrpreckpt(void); extern void smgrsync(void); extern void smgrpostckpt(void); +extern void AtEOXact_SMgr(void); /* internals: move me elsewhere -- ay 7/94 */ -- 2.40.0