]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Avoid writer starvation by adding a RESERVED state to page locks.
authordan <dan@noemail.net>
Mon, 15 May 2017 19:32:58 +0000 (19:32 +0000)
committerdan <dan@noemail.net>
Mon, 15 May 2017 19:32:58 +0000 (19:32 +0000)
FossilOrigin-Name: 9b7f80246f2b9921483ab23457865e783ee70b93f67bcecc0c16516447a05875

manifest
manifest.uuid
src/server.c
src/wal.c

index f38012546abb8e003bf89676b5cc2522eecd9aa7..f0ca52167147ccd0b16e81c0fd2624ccc9f0cec8 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Avoid\srunning\srecovery\swhile\sthere\sis\sanother\sread/write\sclient.
-D 2017-05-13T19:07:10.813
+C Avoid\swriter\sstarvation\sby\sadding\sa\sRESERVED\sstate\sto\spage\slocks.
+D 2017-05-15T19:32:58.633
 F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 6a8c838220f7c00820e1fc0ac1bccaaa8e5676067e1dbfa1bafa7a4ffecf8ae6
@@ -403,7 +403,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c 3e518b962d932a997fae373366880fc028c75706
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c 4f0adefaa5e9417459b07757e0f6060cac97930a86f0fba9797bab233ced66c0
-F src/server.c a1732bcde85f799278c70cd4ae26adf91a7352bad5e58a7a2e5a8092d07c65fd
+F src/server.c c7ba48720cd7520cc409dc926fb85d79fd70183ca5b6424c891d4e47cd184e3f
 F src/server.h e1ce2da1e4d21f335904539e3f98a7c24e015e1201b4bb16d61f0044b8bd2884
 F src/shell.c e5950029da103c5d378e71d548759459b9a7fc76177a71562c22082c705745ab
 F src/sqlite.h.in 8d126e4cfbd1f4bc6f4043aacd77f78b45613e7d630185d49a5d099394247483
@@ -482,7 +482,7 @@ F src/vdbesort.c e72fe02a2121386ba767ede8942e9450878b8fc873abf3d1b6824485f092570
 F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834
 F src/vtab.c 35b9bdc2b41de32a417141d12097bcc4e29a77ed7cdb8f836d1d2305d946b61b
 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
-F src/wal.c bbf37fd26ca1da580b31581b0d172a202bd0931c45f93ae8b09c15ead60232da
+F src/wal.c 8f71654244baf38b95a277e79677d5444737c326182ba81476bc101c001f2e07
 F src/wal.h 739d92494eb18b6d8f3e353e66c10eb8f94534bafd336ece9f3f60235317ea08
 F src/walker.c b71a992b413b3a022572eccf29ef4b4890223791
 F src/where.c c6352f15be5031907c68bcbde96cad1a6da20e9f4051d10168a59235de9a8566
@@ -1584,7 +1584,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P cbf44ed9758d577e1450b53e645b73c9ca1ee29d2354ce6375c234a41a063400
-R d5d776736d26c48307deb362071ad1ff
+P a38858a24c6edaa05966b7e158e603bcbde8b05c6233e3bb005cfb32bc91ea06
+R b8e235bc4c011a85aed7a341ea53efc6
 U dan
-Z 76d66a6c513b28fb6f7860f0dae11a81
+Z 6c36f74987eb2c83b0e8e4c18223c9e3
index a46e98bb53fff37c39bfe1daeabb0cfbd17d80c6..5993503087c6ebfb667713be5b62c59ecb0dbf95 100644 (file)
@@ -1 +1 @@
-a38858a24c6edaa05966b7e158e603bcbde8b05c6233e3bb005cfb32bc91ea06
\ No newline at end of file
+9b7f80246f2b9921483ab23457865e783ee70b93f67bcecc0c16516447a05875
\ No newline at end of file
index 440f717f9bc6a617c72318faec21179cb899a8b3..f455490fd781afb5618db4e51e4d0422ab1377cd 100644 (file)
 **
 **    N*4 bytes - Page locking slots. N is HMA_PAGELOCK_SLOTS.
 **
-** Page lock slot format:
+** Page-locking slot format:
 **
-**    Least significant HMA_CLIENT_SLOTS used for read-locks. If bit 0 is set,
-**    client 0 holds a read-lock.
+**   Each page-locking slot provides SHARED/RESERVED/EXCLUSIVE locks on a
+**   single page. A RESERVED lock is similar to a RESERVED in SQLite's
+**   rollback mode - existing SHARED locks may continue but new SHARED locks
+**   may not be established. As in rollback mode, EXCLUSIVE and RESERVED 
+**   locks are mutually exclusive.
 **
-**    If (v) is the value of the locking slot and (v>>HMA_CLIENT_SLOTS) is
-**    not zero, then the write-lock holder is client ((v>>HMA_CLIENT_SLOTS)-1).
+**   Each 32-bit locking slot is divided into two sections - a bitmask for
+**   read-locks and a single integer field for the write lock. The bitmask
+**   occupies the least-significant 27 bits of the slot. The integer field
+**   occupies the remaining 5 bits (so that it can store values from 0-31).
 **
+**   Each client has a unique integer client id. Currently these range from
+**   0-15 (maximum of 16 concurrent connections). The page-locking slot format
+**   allows this to be increased to 0-26 (maximum of 26 connections). To
+**   take a SHARED lock, the corresponding bit is set in the locking slot
+**   bitmask:
+**
+**     slot = slot | (1 << iClient);
+**
+**   To take an EXCLUSIVE or RESERVED lock, the integer part of the locking
+**   slot is set to the client-id of the locker plus one (a value of zero 
+**   indicates that no connection holds a RESERVED or EXCLUSIVE lock):
+**
+**     slot = slot | ((iClient+1) << 27)
 */
 
 #ifdef SQLITE_SERVER_EDITION
@@ -46,6 +64,7 @@
 #include "sys/mman.h"
 #include "sys/types.h"
 #include "sys/stat.h"
+#include "errno.h"
 
 typedef struct ServerHMA ServerHMA;
 
@@ -112,6 +131,9 @@ static int posixLock(int fd, int iSlot, int eLock, int bBlock){
   l.l_len = 1;
 
   res = fcntl(fd, (bBlock ? F_SETLKW : F_SETLK), &l);
+  if( res && bBlock && errno==EDEADLK ){
+    return SQLITE_BUSY_DEADLOCK;
+  }
   return (res==0 ? SQLITE_OK : SQLITE_BUSY);
 }
 
@@ -366,7 +388,7 @@ static int serverOvercomeLock(
   int rc = SQLITE_OK;
   int iBlock = ((int)(v>>HMA_CLIENT_SLOTS))-1;
 
-  if( iBlock<0 ){
+  if( iBlock<0 || iBlock==p->iClient ){
     for(iBlock=0; iBlock<HMA_CLIENT_SLOTS; iBlock++){
       if( iBlock!=p->iClient && (v & (1<<iBlock)) ) break;
     }
@@ -448,6 +470,15 @@ int sqlite3ServerReleaseWriteLocks(Server *p){
   return rc;
 }
 
+/*
+** Return the client id of the client that currently holds the EXCLUSIVE
+** or RESERVED lock according to page-locking slot value v. Or -1 if no
+** client holds such a lock.
+*/
+int serverWriteLocker(u32 v){
+  return ((int)(v >> HMA_CLIENT_SLOTS)) - 1;
+}
+
 /*
 ** Lock page pgno for reading (bWrite==0) or writing (bWrite==1).
 **
@@ -456,6 +487,8 @@ int sqlite3ServerReleaseWriteLocks(Server *p){
 */
 int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
   int rc = SQLITE_OK;
+  int bReserved = 0;
+  u32 *pSlot = serverPageLockSlot(p, pgno);
 
   /* Grow the aLock[] array, if required */
   if( p->nLock==p->nAlloc ){
@@ -470,7 +503,6 @@ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
     }
   }
   if( rc==SQLITE_OK ){
-    u32 *pSlot = serverPageLockSlot(p, pgno);
     u32 v = *pSlot;
 
     /* Check if the required lock is already held. If so, exit this function
@@ -482,13 +514,28 @@ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
     }else{
       if( v & (1<<p->iClient) ) goto server_lock_out;
     }
-
     p->aLock[p->nLock++] = pgno;
+
     while( 1 ){
       u32 n;
+      int w;
+      u32 mask = (bWrite ? (((1<<HMA_CLIENT_SLOTS)-1) & ~(1<<p->iClient)) : 0);
 
-      while( (bWrite && (v & ~(1 << p->iClient))) || (v >> HMA_CLIENT_SLOTS) ){
+      while( ((w = serverWriteLocker(v))>=0 && w!=p->iClient) || (v & mask) ){
         int bRetry = 0;
+
+        if( w<0 && bWrite && bBlock ){
+          /* Attempt a RESERVED lock before anything else */
+          n = v | ((p->iClient+1) << HMA_CLIENT_SLOTS);
+          assert( serverWriteLocker(n)==p->iClient );
+          if( __sync_val_compare_and_swap(pSlot, v, n)!=v ){
+            v = *pSlot;
+            continue;
+          }
+          v = n;
+          bReserved = 1;
+        }
+
         rc = serverOvercomeLock(p, bWrite, bBlock, v, &bRetry);
         if( rc!=SQLITE_OK ) goto server_lock_out;
         if( bRetry==0 ){
@@ -497,6 +544,7 @@ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
           rc = SQLITE_BUSY_DEADLOCK;
           goto server_lock_out;
         }
+
         v = *pSlot;
       }
 
@@ -510,6 +558,17 @@ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
   }
 
 server_lock_out:
+  if( rc!=SQLITE_OK && bReserved ){
+    u32 n;
+    u32 v;
+    do{
+      v = *pSlot;
+      assert( serverWriteLocker(v)==p->iClient );
+      n = v & ((1<<HMA_CLIENT_SLOTS)-1);
+    }while( __sync_val_compare_and_swap(pSlot, v, n)!=v );
+  }
+
+  assert( rc!=SQLITE_OK || sqlite3ServerHasLock(p, pgno, bWrite) );
   return rc;
 }
 
index a5bb088f660921c27fd541cbf1465a57003f789c..954a32b5579a05a633d47dcc7d845045110e235c 100644 (file)
--- a/src/wal.c
+++ b/src/wal.c
@@ -3122,6 +3122,7 @@ int sqlite3WalFrames(
     if( rc!=SQLITE_OK ){
       return rc;
     }
+    assert( sqlite3ServerHasLock(pWal->pServer, 0, 1) );
   }
   assert( walIsServer(pWal)==0 || sqlite3ServerHasLock(pWal->pServer, 0, 1) );
 
@@ -3397,6 +3398,8 @@ int sqlite3WalCheckpoint(
   */
   if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
     if( walIsServer(pWal) ){
+      rc = sqlite3ServerBegin(pWal->pServer);
+      if( rc!=SQLITE_OK ) goto ckpt_out;
       if( eMode>=SQLITE_CHECKPOINT_RESTART ){
         /* Exclusive lock on page 1. This is exclusive access to the db. */
         rc = sqlite3ServerLock(pWal->pServer, 1, 1, 1);
@@ -3451,6 +3454,7 @@ int sqlite3WalCheckpoint(
   }
 
   /* Release the locks. */
+ ckpt_out:
   sqlite3WalEndWriteTransaction(pWal);
   walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
   pWal->ckptLock = 0;