]> granicus.if.org Git - postgresql/commitdiff
ReleaseRelationBuffers() failed to check for I/O in progress on a buffer
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 22 Nov 1999 01:19:42 +0000 (01:19 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 22 Nov 1999 01:19:42 +0000 (01:19 +0000)
it wants to release.  This leads to a race condition: does the backend
that's trying to flush the buffer do so before the one that's deleting the
relation does so?  Usually no problem, I expect, but on occasion this could
lead to hard-to-reproduce complaints from md.c, especially mdblindwrt.

src/backend/storage/buffer/bufmgr.c

index bd5773e98e5345715f77d79b6b8bf77aa53eb9d6..8b71dc288c4272a9b137e8ce184ea77a7cd6a678 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/storage/buffer/bufmgr.c,v 1.66 1999/11/16 04:13:56 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/storage/buffer/bufmgr.c,v 1.67 1999/11/22 01:19:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1056,8 +1056,13 @@ BufferSync()
 
 
 /*
- * WaitIO -- Block until the IO_IN_PROGRESS flag on 'buf'
- *             is cleared.  Because IO_IN_PROGRESS conflicts are
+ * WaitIO -- Block until the IO_IN_PROGRESS flag on 'buf' is cleared.
+ *
+ * Should be entered with buffer manager spinlock held; releases it before
+ * waiting and re-acquires it afterwards.
+ *
+ * OLD NOTES:
+ *             Because IO_IN_PROGRESS conflicts are
  *             expected to be rare, there is only one BufferIO
  *             lock in the entire system.      All processes block
  *             on this semaphore when they try to use a buffer
@@ -1069,15 +1074,13 @@ BufferSync()
  *             is simple, but efficient enough if WaitIO is
  *             rarely called by multiple processes simultaneously.
  *
- *     ProcSleep atomically releases the spinlock and goes to
- *             sleep.
- *
- *     Note: there is an easy fix if the queue becomes long.
- *             save the id of the buffer we are waiting for in
- *             the queue structure.  That way signal can figure
- *             out which proc to wake up.
+ * NEW NOTES:
+ *             The above is true only on machines without test-and-set
+ *             semaphores (which we hope are few, these days).  On better
+ *             hardware, each buffer has a spinlock that we can wait on.
  */
 #ifdef HAS_TEST_AND_SET
+
 static void
 WaitIO(BufferDesc *buf, SPINLOCK spinlock)
 {
@@ -1087,7 +1090,8 @@ WaitIO(BufferDesc *buf, SPINLOCK spinlock)
        SpinAcquire(spinlock);
 }
 
-#else                                                  /* HAS_TEST_AND_SET */
+#else                                                  /* !HAS_TEST_AND_SET */
+
 IpcSemaphoreId WaitIOSemId;
 IpcSemaphoreId WaitCLSemId;
 
@@ -1387,7 +1391,11 @@ RelationGetNumberOfBlocks(Relation relation)
  *
  *             this function unmarks all the dirty pages of a relation
  *             in the buffer pool so that at the end of transaction
- *             these pages will not be flushed.
+ *             these pages will not be flushed.  This is used when the
+ *             relation is about to be deleted.  We assume that the caller
+ *             holds an exclusive lock on the relation, which should assure
+ *             that no new buffers will be acquired for the rel meanwhile.
+ *
  *             XXX currently it sequentially searches the buffer pool, should be
  *             changed to more clever ways of searching.
  * --------------------------------------------------------------------
@@ -1395,8 +1403,9 @@ RelationGetNumberOfBlocks(Relation relation)
 void
 ReleaseRelationBuffers(Relation rel)
 {
+       Oid                     relid = RelationGetRelid(rel);
+       bool            holding = false;
        int                     i;
-       int                     holding = 0;
        BufferDesc *buf;
 
        if (rel->rd_myxactonly)
@@ -1404,9 +1413,8 @@ ReleaseRelationBuffers(Relation rel)
                for (i = 0; i < NLocBuffer; i++)
                {
                        buf = &LocalBufferDescriptors[i];
-                       if ((buf->flags & BM_DIRTY) &&
-                               (buf->tag.relId.relId == RelationGetRelid(rel)))
-                               buf->flags &= ~BM_DIRTY;
+                       if (buf->tag.relId.relId == relid)
+                               buf->flags &= ~ ( BM_DIRTY | BM_JUST_DIRTIED);
                }
                return;
        }
@@ -1417,21 +1425,47 @@ ReleaseRelationBuffers(Relation rel)
                if (!holding)
                {
                        SpinAcquire(BufMgrLock);
-                       holding = 1;
+                       holding = true;
                }
-               if ((buf->flags & BM_DIRTY) &&
-                       (buf->tag.relId.dbId == MyDatabaseId) &&
-                       (buf->tag.relId.relId == RelationGetRelid(rel)))
+       recheck:
+               if (buf->tag.relId.dbId == MyDatabaseId &&
+                       buf->tag.relId.relId == relid)
                {
-                       buf->flags &= ~BM_DIRTY;
+                       /*
+                        * If there is I/O in progress, better wait till it's done;
+                        * don't want to delete the relation out from under someone
+                        * who's just trying to flush the buffer!
+                        */
+                       if (buf->flags & BM_IO_IN_PROGRESS)
+                       {
+                               WaitIO(buf, BufMgrLock);
+                               /* By now, the buffer very possibly belongs to some other
+                                * rel, so check again before proceeding.
+                                */
+                               goto recheck;
+                       }
+                       /* Now we can do what we came for */
+                       buf->flags &= ~ ( BM_DIRTY | BM_JUST_DIRTIED);
+                       CommitInfoNeedsSave[i - 1] = 0;
+                       /*
+                        * Release any refcount we may have.
+                        *
+                        * This is very probably dead code, and if it isn't then it's
+                        * probably wrong.  I added the Assert to find out --- tgl 11/99.
+                        */
                        if (!(buf->flags & BM_FREE))
                        {
+                               /* Assert checks that buffer will actually get freed! */
+                               Assert(PrivateRefCount[i - 1] == 1 &&
+                                          buf->refcount == 1);
+                               /* ReleaseBuffer expects we do not hold the lock at entry */
                                SpinRelease(BufMgrLock);
-                               holding = 0;
+                               holding = false;
                                ReleaseBuffer(i);
                        }
                }
        }
+
        if (holding)
                SpinRelease(BufMgrLock);
 }