]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Close un-owned SMgrRelations at transaction end.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Oct 2012 16:38:33 +0000 (12:38 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Oct 2012 16:38:33 +0000 (12:38 -0400)
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
src/backend/postmaster/bgwriter.c
src/backend/postmaster/walwriter.c
src/backend/storage/smgr/smgr.c
src/include/storage/smgr.h

index a38fed18fb69158995c88cf438df00965c6b9f64..fb91d62ce836c8158535dff4f8299e424018f67e 100644 (file)
@@ -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);
index c850033537195269bb306aa8846d38d2ea507ec1..83c13e252c446a97399a497eba6b04fd58ee6c5f 100644 (file)
@@ -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);
 
index d0d7c9bebf03ea4f0a744710b77195fea3a61a71..80991f6f5677f70266da1a4c5464dae6ef1e9517 100644 (file)
@@ -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);
 
index a6610bf4f82d1a4174a9bfd27a226c55ef0c3ba4..a950b853235ce797eae11d64825685f851be672c 100644 (file)
@@ -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);
+       }
+}
index 47c4c1640a31f44a015a1562a01d250c2143afc1..9a135cb5a204f3d65b3a1e23aa505e735083993e 100644 (file)
@@ -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 */