]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Supports SQLITE_ENABLE_SETLK_TIMEOUT on windows. Does not work properly yet.
authordan <Dan Kennedy>
Fri, 22 Nov 2024 21:24:08 +0000 (21:24 +0000)
committerdan <Dan Kennedy>
Fri, 22 Nov 2024 21:24:08 +0000 (21:24 +0000)
FossilOrigin-Name: 737ca8a9fb9dc74b28f2186d93c5101463497445d0fabba3def61fee29abf2c8

Makefile.msc
manifest
manifest.uuid
src/mutex_w32.c
src/os_unix.c
src/os_win.c
test/symlink2.test
test/wal.test

index 32b8143768103d540c66086c5aba58de9327bf35..c56f2d30519a90e304cab0d17f99561e491917d2 100644 (file)
@@ -292,6 +292,12 @@ SESSION = 0
 RBU = 0
 !ENDIF
 
+# Set this to non-0 to enable support for blocking locks.
+#
+!IFNDEF SETLK_TIMEOUT
+SETLK_TIMEOUT = 0
+!ENDIF
+
 # Set the source code file to be used by executables and libraries when
 # they need the amalgamation.
 #
@@ -448,6 +454,10 @@ EXT_FEATURE_FLAGS =
 !ENDIF
 !ENDIF
 
+!IF $(SETLK_TIMEOUT)!=0
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SETLK_TIMEOUT
+!ENDIF
+
 ###############################################################################
 ############################### END OF OPTIONS ################################
 ###############################################################################
index a1f3f97ae37d8cd511c329debb949735183af224..1a7b05d77f21ef2cabdc3ad4feef37a8928f5818 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,11 +1,11 @@
-C Fix\sanother\sissue\sin\sargument\sexpansion\son\sWindows\sfor\stclsqlite3.c\sin\ninterpreter\smode.\s\sProblem\sintroduced\sby\scheck-in\s[9b87ea219bce5689]\sand\nunfixed\sby\s[cd942dce148c9d8f].
-D 2024-11-22T17:41:00.227
+C Supports\sSQLITE_ENABLE_SETLK_TIMEOUT\son\swindows.\sDoes\snot\swork\sproperly\syet.
+D 2024-11-22T21:24:08.721
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md e108e1e69ae8e8a59e93c455654b8ac9356a11720d3345df2a4743e9590fb20d
 F Makefile.in 85f2c740cadf969abbd9a6210b8b76636dbf231b9d3efe977b060c3055fac5b9
 F Makefile.linux-generic bd3e3cacd369821a6241d4ea1967395c962dfe3057e38cb0a435cee0e8b789d0
-F Makefile.msc a92237976eb92c5efaa0dd2524746aec12c196e12df8d4dbff9543a4648c3312
+F Makefile.msc 9a975438b8e06da44bc169b74aa9601cd48da52abd2c88e8a349c7d82b59d250
 F README.md c3c0f19532ce28f6297a71870f3c7b424729f0e6d9ab889616d3587dd2332159
 F VERSION 8dc0c3df15fd5ff0622f88fc483533fce990b1cbb2f5fb9fdfb4dbd71eef2889
 F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5
@@ -751,15 +751,15 @@ F src/mutex.c 06bcd9c3dbf2d9b21fcd182606c00fafb9bfe0287983c8e17acd13d2c81a2fa9
 F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a
 F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
 F src/mutex_unix.c f7ee5a2061a4c11815a2bf4fc0e2bfa6fb8d9dc89390eb613ca0cec32fc9a3d1
-F src/mutex_w32.c 28f8d480387db5b2ef5248705dd4e19db0cfc12c3ba426695a7d2c45c48e6885
+F src/mutex_w32.c db182bf5aac08a16fbf5916d94974f5a11556fe150142fcabe36d6454e0d93a1
 F src/notify.c 57c2d1a2805d6dee32acd5d250d928ab94e02d76369ae057dee7d445fd64e878
 F src/os.c 509452169d5ea739723e213b8e2481cf0e587f0e88579a912d200db5269f5f6d
 F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
 F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
-F src/os_unix.c d2edbd92b07a3f778c2defa8a2e9d75acceb6267bda56948c41e8cdda65224d6
-F src/os_win.c 49c7725b500f5867e8360e75eeb30f9d70b62fa1f05c8a101da627210578df32
+F src/os_unix.c d4a33e8fbd1c6eb722a21b6ce1eee1213ec856170a2f256d99f3d2978f054f5a
+F src/os_win.c 2ed170fb6dba67952b7f07dfee71bb854463fb2fb51b0289fce5dec0fd075b0f
 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
 F src/pager.c 9656ad4e8331efb8a4f94f7a0c6440b98caea073950a367ea0c728a53b8e62c9
 F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a
@@ -1702,7 +1702,7 @@ F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27c
 F test/swarmvtab3.test 41a3ab47cb7a834d4e5336425103b617410a67bb95d335ef536f887587ece073
 F test/swarmvtabfault.test 8a67a9f27c61073a47990829e92bc0c64420a807cb642b15a25f6c788210ed95
 F test/symlink.test 4368af0e213dd6e726a6240a16f2bb96a5a58f83f2d5d60652f27547b28cbf06
-F test/symlink2.test bf932ff7fe95c9dbb39d2a990df9098b0ea943233c97e40098e0a8d6b559a96f
+F test/symlink2.test 0b7734533f198bbc46fb8ea984ffaea537c8ee949eaba8805a92ed9969573956
 F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d4333092
 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039
 F test/syscall.test a067468b43b8cb2305e9f9fe414e5f40c875bb5d2cba5f00b8154396e95fcf37
@@ -1986,7 +1986,7 @@ F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad
 F test/vtabdistinct.test 7688f0889358f849fd60bbfde1ded38b014b18066076d4bfbb75395804dfe072
 F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7edca12
 F test/vtabrhs1.test 9b5ecbc74a689500c33a4b2b36761f9bcc22fcc4e3f9d21066ee0c9c74cf5f6c
-F test/wal.test 519c550255c78f55959e9159b93ebbfad2b4e9f36f5b76284da41f572f9d27da
+F test/wal.test 3628a18ed2ba1cad58978802381f89e6076d225d5c93836d3eed464f867fa288
 F test/wal2.test e89ca97593b5e92849039f6b68ce1719a853ef20fa22c669ec1ac452fbc31cab
 F test/wal3.test 5de023bb862fd1eb9d2ad26fa8d9c43abb5370582e5b08b2ae0d6f93661bc310
 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c
@@ -2199,8 +2199,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 3d6ae13805bdba4c73b7443f20073264cdd157299cb911228600e1528a136bb1
-R f112fd2beffd3eb814301c3676676b3e
-U drh
-Z 113c5b5aab1719624bc6c7ebe0ae9b2d
+P 0fe1622cec95b7ebecc127ee57a08113d3da1dadbe72c03a13d6751b3043e50f
+R ee8b39ab71f10eb6a0db7f5e0c48bbd2
+T *branch * win32-enable-setlk
+T *sym-win32-enable-setlk *
+T -sym-trunk *
+U dan
+Z e8f9e025559e2dfd331c377d699a5f1c
 # Remove this line to create a well-formed Fossil manifest.
index 8cd2bbe8d49f6a5fd65b708fd27636a42d007a53..3a08657fecf603edcfad953514fbdf1132e681f5 100644 (file)
@@ -1 +1 @@
-0fe1622cec95b7ebecc127ee57a08113d3da1dadbe72c03a13d6751b3043e50f
+737ca8a9fb9dc74b28f2186d93c5101463497445d0fabba3def61fee29abf2c8
index 7eb5b50be15c749d3f828f8483e5233592d68f36..7b411018f58a0e0cb6ce2bbeda0825aadfc5b073 100644 (file)
@@ -314,22 +314,12 @@ static int winMutexTry(sqlite3_mutex *p){
   /*
   ** The sqlite3_mutex_try() routine is very rarely used, and when it
   ** is used it is merely an optimization.  So it is OK for it to always
-  ** fail.
-  **
-  ** The TryEnterCriticalSection() interface is only available on WinNT.
-  ** And some windows compilers complain if you try to use it without
-  ** first doing some #defines that prevent SQLite from building on Win98.
-  ** For that reason, we will omit this optimization for now.  See
-  ** ticket #2685.
+  ** fail on some platforms. But - it is required for ENABLE_SETLK_TIMEOUT
+  ** builds.
   */
 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0400
   assert( winMutex_isInit==1 );
-  assert( winMutex_isNt>=-1 && winMutex_isNt<=1 );
-  if( winMutex_isNt<0 ){
-    winMutex_isNt = sqlite3_win32_is_nt();
-  }
-  assert( winMutex_isNt==0 || winMutex_isNt==1 );
-  if( winMutex_isNt && TryEnterCriticalSection(&p->mutex) ){
+  if( sqlite3_win32_is_nt() && TryEnterCriticalSection(&p->mutex) ){
 #ifdef SQLITE_DEBUG
     p->owner = tid;
     p->nRef++;
index b1996278c81cd0fa8e8d3d293c114863e293e000..77855a8dd0905a23bf46251b9ba42769b48df62c 100644 (file)
@@ -4290,7 +4290,6 @@ static int unixGetpagesize(void){
 **
 **      nRef
 **
-** The following fields are read-only after the object is created:
 **
 **      hShm
 **      zFilename
index 8ce1647f6083bb3c1b3ebaca06aabe99ebdd9017..ff094e8d14eaf77a371b6b1fbe3059582d8a2560 100644 (file)
@@ -287,6 +287,9 @@ struct winFile {
   sqlite3_int64 mmapSize;       /* Size of mapped region */
   sqlite3_int64 mmapSizeMax;    /* Configured FCNTL_MMAP_SIZE value */
 #endif
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  unsigned iBusyTimeout;        /* Wait this many millisec on locks */
+#endif
 };
 
 /*
@@ -1453,6 +1456,9 @@ int sqlite3_win32_is_nt(void){
   }
   return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2;
 #elif SQLITE_TEST
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  return 1;
+#endif
   return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2;
 #else
   /*
@@ -2540,6 +2546,73 @@ static BOOL winLockFile(
 #endif
 }
 
+/*
+** Lock a region of nByte bytes starting at offset offset of file phFile.
+** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock
+** otherwise. If nMs is greater than zero and the lock cannot be obtained
+** immediately, block for that many ms before giving up.
+**
+** If parameter pMutex is not NULL, then
+**
+** This function returns SQLITE_OK if the lock is obtained successfully. If
+** some other process holds the lock, SQLITE_BUSY is returned if nMs==0, or
+** SQLITE_BUSY_TIMEOUT otherwise. Or, if an error occurs, SQLITE_IOERR.
+*/
+static int winLockFileTimeout(
+  LPHANDLE phFile,
+  sqlite3_mutex *pMutex,
+  DWORD offset,
+  DWORD nByte,
+  int bExcl,
+  int nMs
+){
+  DWORD flags = LOCKFILE_FAIL_IMMEDIATELY | (bExcl?LOCKFILE_EXCLUSIVE_LOCK:0);
+  int rc = SQLITE_OK;
+  BOOL ret;
+
+#if !defined(SQLITE_ENABLE_SETLK_TIMEOUT)
+  ret = winLockFile(phFile, flags, offset, 0, nByte, 0);
+#else
+  if( !osIsNT() ){
+    ret = winLockFile(phFile, flags, offset, 0, nByte, 0);
+  }else{
+    OVERLAPPED ovlp;
+    memset(&ovlp, 0, sizeof(OVERLAPPED));
+    ovlp.Offset = offset;
+    if( nMs>0 ){
+      ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+      if( ovlp.hEvent==NULL ){
+        return SQLITE_IOERR;
+      }
+    }
+
+    ret = osLockFileEx(*phFile, flags, 0, nByte, 0, &ovlp);
+
+    if( nMs>0 ){
+      if( !ret ){
+        DWORD res = WaitForSingleObject(ovlp.hEvent, (DWORD)nMs);
+        if( res==WAIT_OBJECT_0 ){
+          /* Successfully obtained the lock. */
+          ret = TRUE;
+        }else if( res==WAIT_TIMEOUT ){
+          /* Timeout */
+          rc = SQLITE_BUSY_TIMEOUT;
+        }else{
+          /* Some other error has occurred */
+          rc = SQLITE_IOERR;
+        }
+      }
+      CloseHandle(ovlp.hEvent);
+    }
+  }
+#endif  /* defined(SQLITE_ENABLE_SETLK_TIMEOUT) */
+
+  if( rc==SQLITE_OK && !ret ){
+    rc = SQLITE_BUSY;
+  }
+  return rc;
+}
+
 /*
 ** Unlock a file region.
  */
@@ -3640,6 +3713,22 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
       return rc;
     }
 #endif
+
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+    case SQLITE_FCNTL_LOCK_TIMEOUT: {
+      int iOld = pFile->iBusyTimeout;
+#if SQLITE_ENABLE_SETLK_TIMEOUT==1
+      pFile->iBusyTimeout = *(int*)pArg;
+#elif SQLITE_ENABLE_SETLK_TIMEOUT==2
+      pFile->iBusyTimeout = !!(*(int*)pArg);
+#else
+# error "SQLITE_ENABLE_SETLK_TIMEOUT must be set to 1 or 2"
+#endif
+      *(int*)pArg = iOld;
+      return SQLITE_OK;
+    }
+#endif
+
   }
   OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h));
   return SQLITE_NOTFOUND;
@@ -3720,13 +3809,31 @@ static int winShmMutexHeld(void) {
 **
 ** The following fields are read-only after the object is created:
 **
-**      fid
+**      hFile
 **      zFilename
 **
 ** Either winShmNode.mutex must be held or winShmNode.nRef==0 and
 ** winShmMutexHeld() is true when reading or writing any other field
 ** in this structure.
 **
+** aMutex[SQLITE_SHM_NLOCK]:
+**   Normally, when SQLITE_ENABLE_SETLK_TIMEOUT is not defined, mutex 
+**   winShmNode.mutex is used to serialize calls to the xShmLock() 
+**   method.
+**
+**   For SQLITE_ENABLE_SETLK_TIMEOUT builds, xShmLock() only takes the
+**   mutexes in the aMutex[] array that correspond to locks being taken
+**   or released. This means that:
+**
+**     *  Modifying the winShmNode.pFirst list requires holding *all*
+**        the locks in the aMutex[] array.
+**
+**     *  Reads and writes to winShm.sharedMask and winShm.exclMask must
+**        use AtomicLoad() and AtomicStore(). This is because it may be
+**        read by other threads while it is being modified.
+**
+**   TODO: winShmNode.mutex is held for the space of time when LockFileEx() 
+**   is called on winShmNode.hFile.
 */
 struct winShmNode {
   sqlite3_mutex *mutex;      /* Mutex to access this object */
@@ -3747,11 +3854,38 @@ struct winShmNode {
   int nRef;                  /* Number of winShm objects pointing to this */
   winShm *pFirst;            /* All winShm objects pointing to this */
   winShmNode *pNext;         /* Next in list of all winShmNode objects */
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  sqlite3_mutex *aMutex[SQLITE_SHM_NLOCK];
+#endif
 #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
   u8 nextShmId;              /* Next available winShm.id value */
 #endif
 };
 
+/* 
+** Enter/leave the mutex required to modify the winShmNode.pFirst list.
+*/
+static void winShmListMutexEnter(winShmNode *pShmNode){
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  int ii;
+  for(ii=0; ii<SQLITE_SHM_NLOCK; ii++){
+    sqlite3_mutex_enter(pShmNode->aMutex[ii]);
+  }
+#else
+  sqlite3_mutex_enter(pShmNode->mutex);
+#endif
+}
+static void winShmListMutexLeave(winShmNode *pShmNode){
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  int ii;
+  for(ii=0; ii<SQLITE_SHM_NLOCK; ii++){
+    sqlite3_mutex_leave(pShmNode->aMutex[ii]);
+  }
+#else
+  sqlite3_mutex_leave(pShmNode->mutex);
+#endif
+}
+
 /*
 ** A global array of all winShmNode objects.
 **
@@ -3796,39 +3930,48 @@ struct winShm {
 #define WINSHM_RDLCK  2
 #define WINSHM_WRLCK  3
 static int winShmSystemLock(
-  winShmNode *pFile,    /* Apply locks to this open shared-memory segment */
+  winFile *pDbFd,       /* Apply locks to this open shared-memory segment */
   int lockType,         /* WINSHM_UNLCK, WINSHM_RDLCK, or WINSHM_WRLCK */
   int ofst,             /* Offset to first byte to be locked/unlocked */
   int nByte             /* Number of bytes to lock or unlock */
 ){
+  winShmNode *pShmNode = pDbFd->pShm->pShmNode;
   int rc = 0;           /* Result code form Lock/UnlockFileEx() */
 
   /* Access to the winShmNode object is serialized by the caller */
-  assert( pFile->nRef==0 || sqlite3_mutex_held(pFile->mutex) );
+  /* assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->mutex) ); */
 
   OSTRACE(("SHM-LOCK file=%p, lock=%d, offset=%d, size=%d\n",
-           pFile->hFile.h, lockType, ofst, nByte));
+           pShmNode->hFile.h, lockType, ofst, nByte));
 
   /* Release/Acquire the system-level lock */
   if( lockType==WINSHM_UNLCK ){
-    rc = winUnlockFile(&pFile->hFile.h, ofst, 0, nByte, 0);
+    int ret = winUnlockFile(&pShmNode->hFile.h, ofst, 0, nByte, 0);
+    if( ret==0 ){
+      pShmNode->lastErrno =  osGetLastError();
+      rc = SQLITE_ERROR;
+    }
   }else{
     /* Initialize the locking parameters */
+#if SQLITE_ENABLE_SETLK_TIMEOUT
+    rc = winLockFileTimeout(&pShmNode->hFile.h, pShmNode->mutex, ofst, nByte, 
+        (lockType==WINSHM_WRLCK), pDbFd->iBusyTimeout);
+#else
     DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY;
     if( lockType == WINSHM_WRLCK ) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK;
-    rc = winLockFile(&pFile->hFile.h, dwFlags, ofst, 0, nByte, 0);
-  }
-
-  if( rc!= 0 ){
-    rc = SQLITE_OK;
-  }else{
-    pFile->lastErrno =  osGetLastError();
-    rc = SQLITE_BUSY;
+    rc = winLockFile(&pShmNode->hFile.h, dwFlags, ofst, 0, nByte, 0);
+    if( rc!=0 ){
+      rc = SQLITE_OK;
+    }else{
+      pShmNode->lastErrno =  osGetLastError();
+      rc = SQLITE_BUSY;
+    }
+#endif
   }
 
   OSTRACE(("SHM-LOCK file=%p, func=%s, errno=%lu, rc=%s\n",
-           pFile->hFile.h, (lockType == WINSHM_UNLCK) ? "winUnlockFile" :
-           "winLockFile", pFile->lastErrno, sqlite3ErrName(rc)));
+           pShmNode->hFile.h, (lockType == WINSHM_UNLCK) ? "winUnlockFile" :
+           "winLockFile", pShmNode->lastErrno, sqlite3ErrName(rc)));
 
   return rc;
 }
@@ -3854,6 +3997,12 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
     if( p->nRef==0 ){
       int i;
       if( p->mutex ){ sqlite3_mutex_free(p->mutex); }
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+      /* Free the contents of the winShmNode.aMutex[] array */
+      for(i=0; i<SQLITE_SHM_NLOCK; i++){
+        sqlite3_mutex_free(p->aMutex[i]);
+      }
+#endif
       for(i=0; i<p->nRegion; i++){
         BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap);
         OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n",
@@ -3886,34 +4035,35 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
 }
 
 /*
-** The DMS lock has not yet been taken on shm file pShmNode. Attempt to
-** take it now. Return SQLITE_OK if successful, or an SQLite error
-** code otherwise.
+** The DMS lock has not yet been taken on the shm file attached to
+** pDbFd. Attempt to take it now. Return SQLITE_OK if successful, or an 
+** SQLite error code otherwise.
 **
 ** If the DMS cannot be locked because this is a readonly_shm=1
 ** connection and no other process already holds a lock, return
 ** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1.
 */
-static int winLockSharedMemory(winShmNode *pShmNode){
-  int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1);
+static int winLockSharedMemory(winFile *pDbFd){
+  winShmNode *pShmNode = pDbFd->pShm->pShmNode;
+  int rc = winShmSystemLock(pDbFd, WINSHM_WRLCK, WIN_SHM_DMS, 1);
 
   if( rc==SQLITE_OK ){
     if( pShmNode->isReadonly ){
       pShmNode->isUnlocked = 1;
-      winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+      winShmSystemLock(pDbFd, WINSHM_UNLCK, WIN_SHM_DMS, 1);
       return SQLITE_READONLY_CANTINIT;
     }else if( winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){
-      winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+      winShmSystemLock(pDbFd, WINSHM_UNLCK, WIN_SHM_DMS, 1);
       return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(),
                          "winLockSharedMemory", pShmNode->zFilename);
     }
   }
 
   if( rc==SQLITE_OK ){
-    winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+    winShmSystemLock(pDbFd, WINSHM_UNLCK, WIN_SHM_DMS, 1);
   }
 
-  return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1);
+  return winShmSystemLock(pDbFd, WINSHM_RDLCK, WIN_SHM_DMS, 1);
 }
 
 /*
@@ -3975,6 +4125,20 @@ static int winOpenSharedMemory(winFile *pDbFd){
         rc = SQLITE_IOERR_NOMEM_BKPT;
         goto shm_open_err;
       }
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+      /* If SETLK_TIMEOUT is defined, also allocate the array of mutexes
+      ** stored in pShmNode->aMutex[]. */
+      {
+        int ii;
+        for(ii=0; ii<SQLITE_SHM_NLOCK; ii++){
+          pShmNode->aMutex[ii] = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+          if( pShmNode->aMutex[ii]==0 ){
+            rc = SQLITE_IOERR_NOMEM_BKPT;
+            goto shm_open_err;
+          }
+        }
+      }
+#endif
     }
 
     if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){
@@ -3992,7 +4156,10 @@ static int winOpenSharedMemory(winFile *pDbFd){
     }
     if( outFlags==SQLITE_OPEN_READONLY ) pShmNode->isReadonly = 1;
 
-    rc = winLockSharedMemory(pShmNode);
+    p->pShmNode = pShmNode;
+    pDbFd->pShm = p;
+    rc = winLockSharedMemory(pDbFd);
+    pDbFd->pShm = 0;
     if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err;
   }
 
@@ -4012,15 +4179,16 @@ static int winOpenSharedMemory(winFile *pDbFd){
   ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex
   ** mutex.
   */
-  sqlite3_mutex_enter(pShmNode->mutex);
+  winShmListMutexEnter(pShmNode);
   p->pNext = pShmNode->pFirst;
   pShmNode->pFirst = p;
-  sqlite3_mutex_leave(pShmNode->mutex);
+  winShmListMutexLeave(pShmNode);
   return rc;
 
   /* Jump here on any error */
 shm_open_err:
-  winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+  
+  winUnlockFile(&pShmNode->hFile.h, WIN_SHM_DMS, 0, 1, 0);
   winShmPurge(pDbFd->pVfs, 0);      /* This call frees pShmNode if required */
   sqlite3_free(p);
   sqlite3_free(pNew);
@@ -4048,14 +4216,14 @@ static int winShmUnmap(
 
   /* Remove connection p from the set of connections associated
   ** with pShmNode */
-  sqlite3_mutex_enter(pShmNode->mutex);
+  winShmListMutexEnter(pShmNode);
   for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){}
   *pp = p->pNext;
+  winShmListMutexLeave(pShmNode);
 
   /* Free the connection p */
   sqlite3_free(p);
   pDbFd->pShm = 0;
-  sqlite3_mutex_leave(pShmNode->mutex);
 
   /* If pShmNode->nRef has reached 0, then close the underlying
   ** shared-memory file, too */
@@ -4084,7 +4252,7 @@ static int winShmLock(
   winShm *pX;                           /* For looping over all siblings */
   winShmNode *pShmNode;
   int rc = SQLITE_OK;                   /* Result code */
-  u16 mask;                             /* Mask of locks to take or release */
+  u16 mask = (u16)((1U<<(ofst+n)) - (1U<<ofst)); /* Mask of locks to take/untake */
 
   if( p==0 ) return SQLITE_IOERR_SHMLOCK;
   pShmNode = p->pShmNode;
@@ -4098,85 +4266,169 @@ static int winShmLock(
        || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) );
   assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 );
 
-  mask = (u16)((1U<<(ofst+n)) - (1U<<ofst));
-  assert( n>1 || mask==(1<<ofst) );
-  sqlite3_mutex_enter(pShmNode->mutex);
-  if( flags & SQLITE_SHM_UNLOCK ){
-    u16 allMask = 0; /* Mask of locks held by siblings */
-
-    /* See if any siblings hold this same lock */
-    for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
-      if( pX==p ) continue;
-      assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 );
-      allMask |= pX->sharedMask;
-    }
-
-    /* Unlock the system-level locks */
-    if( (mask & allMask)==0 ){
-      rc = winShmSystemLock(pShmNode, WINSHM_UNLCK, ofst+WIN_SHM_BASE, n);
-    }else{
-      rc = SQLITE_OK;
-    }
-
-    /* Undo the local locks */
-    if( rc==SQLITE_OK ){
-      p->exclMask &= ~mask;
-      p->sharedMask &= ~mask;
-    }
-  }else if( flags & SQLITE_SHM_SHARED ){
-    u16 allShared = 0;  /* Union of locks held by connections other than "p" */
+  /* Check that, if this to be a blocking lock, no locks that occur later
+  ** in the following list than the lock being obtained are already held:
+  **
+  **   1. Checkpointer lock (ofst==1).
+  **   2. Write lock (ofst==0).
+  **   3. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK).
+  **
+  ** In other words, if this is a blocking lock, none of the locks that
+  ** occur later in the above list than the lock being obtained may be
+  ** held.
+  **
+  ** It is not permitted to block on the RECOVER lock.
+  */
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+  {
+    u16 lockMask = (p->exclMask|p->sharedMask);
+    assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
+          (ofst!=2)                                   /* not RECOVER */
+       && (ofst!=1 || lockMask==0 || lockMask==2)
+       && (ofst!=0 || lockMask<3)
+       && (ofst<3  || lockMask<(1<<ofst))
+    ));
+  }
+#endif
 
-    /* Find out which shared locks are already held by sibling connections.
-    ** If any sibling already holds an exclusive lock, go ahead and return
-    ** SQLITE_BUSY.
-    */
-    for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
-      if( (pX->exclMask & mask)!=0 ){
-        rc = SQLITE_BUSY;
-        break;
-      }
-      allShared |= pX->sharedMask;
-    }
+  /* Check if there is any work to do. There are three cases:
+  **
+  **    a) An unlock operation where there are locks to unlock,
+  **    b) An shared lock where the requested lock is not already held
+  **    c) An exclusive lock where the requested lock is not already held
+  **
+  ** The SQLite core never requests an exclusive lock that it already holds.
+  ** This is assert()ed below.
+  */
+  assert( flags!=(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK) 
+       || 0==(p->exclMask & mask)
+  );
+  if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask))
+   || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask))
+   || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK))
+  ){
 
-    /* Get shared locks at the system level, if necessary */
-    if( rc==SQLITE_OK ){
-      if( (allShared & mask)==0 ){
-        rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, ofst+WIN_SHM_BASE, n);
+    /* Take the required mutexes. In SETLK_TIMEOUT mode (blocking locks), if
+    ** this is an attempt on an exclusive lock use sqlite3_mutex_try(). If any
+    ** other thread is holding this mutex, then it is either holding or about
+    ** to hold a lock exclusive to the one being requested, and we may
+    ** therefore return SQLITE_BUSY to the caller.
+    **
+    ** Doing this prevents some deadlock scenarios. For example, thread 1 may
+    ** be a checkpointer blocked waiting on the WRITER lock. And thread 2
+    ** may be a normal SQL client upgrading to a write transaction. In this
+    ** case thread 2 does a non-blocking request for the WRITER lock. But -
+    ** if it were to use sqlite3_mutex_enter() then it would effectively
+    ** become a (doomed) blocking request, as thread 2 would block until thread
+    ** 1 obtained WRITER and released the mutex. Since thread 2 already holds
+    ** a lock on a read-locking slot at this point, this breaks the
+    ** anti-deadlock rules (see above).  */
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+    int iMutex;
+    for(iMutex=ofst; iMutex<ofst+n; iMutex++){
+      if( flags==(SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE) ){
+        rc = sqlite3_mutex_try(pShmNode->aMutex[iMutex]);
+        if( rc!=SQLITE_OK ) break;
       }else{
-        rc = SQLITE_OK;
+        sqlite3_mutex_enter(pShmNode->aMutex[iMutex]);
       }
     }
+#else
+    sqlite3_mutex_enter(pShmNode->mutex);
+#endif
 
-    /* Get the local shared locks */
     if( rc==SQLITE_OK ){
-      p->sharedMask |= mask;
-    }
-  }else{
-    /* Make sure no sibling connections hold locks that will block this
-    ** lock.  If any do, return SQLITE_BUSY right away.
-    */
-    for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
-      if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){
-        rc = SQLITE_BUSY;
-        break;
-      }
-    }
+      if( flags & SQLITE_SHM_UNLOCK ){
+        /* Case (a) - unlock.  */
+        u16 allMask = 0; /* Mask of locks held by siblings */
+
+        assert( (p->exclMask & p->sharedMask)==0 );
+        assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask );
+        assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask );
+
+        /* If this is a shared lock, check if any other connection in this
+        ** process is holding the same shared lock. If one or more are, then
+        ** do not unlock the system-level lock held on the file-handle. */
+        if( flags & SQLITE_SHM_SHARED ){
+          for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
+            if( pX==p ) continue;
+            allMask |= AtomicLoad(&pX->sharedMask);
+          }
+        }
+        if( (mask & allMask)==0 ){
+          rc = winShmSystemLock(pDbFd, WINSHM_UNLCK, ofst+WIN_SHM_BASE, n);
+        }
 
-    /* Get the exclusive locks at the system level.  Then if successful
-    ** also mark the local connection as being locked.
-    */
-    if( rc==SQLITE_OK ){
-      rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, ofst+WIN_SHM_BASE, n);
-      if( rc==SQLITE_OK ){
+        /* Undo the local locks */
+        if( rc==SQLITE_OK ){
+          AtomicStore(&p->exclMask, p->exclMask & ~mask);
+          AtomicStore(&p->sharedMask, p->sharedMask & ~mask);
+        }
+      }else if( flags & SQLITE_SHM_SHARED ){
+        /* Case (b) - a shared lock.  */
+        int bLocked = 0;          /* True if process already holds shared lock */
+        assert( n==1 );
+
+        /* See what locks are held by other connections within this process. If
+        ** any are holding an EXCLUSIVE lock, then this call will return
+        ** SQLITE_BUSY. Or, if any are holding a SHARED lock, then there is no
+        ** need to obtain a new system-level lock.  */
+        for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
+          u16 mExcl = AtomicLoad(&pX->exclMask);
+          u16 mShared = AtomicLoad(&pX->sharedMask);
+          if( mExcl & mask ) rc = SQLITE_BUSY;
+          if( mShared & mask ){
+            bLocked = 1;
+          }
+        }
+
+        if( rc==SQLITE_OK && bLocked==0 ){
+          rc = winShmSystemLock(pDbFd, WINSHM_RDLCK, ofst+WIN_SHM_BASE, n);
+        }
+  
+        /* Get the local shared lock */
+        if( rc==SQLITE_OK ){
+          AtomicStore(&p->sharedMask, p->sharedMask | mask);
+        }
+      }else{
+        /* Case (c) - an exclusive lock.  */
+        assert( flags==(SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE) );
         assert( (p->sharedMask & mask)==0 );
-        p->exclMask |= mask;
+        assert( (p->exclMask & mask)==0 );
+  
+        /* Make sure no sibling connections hold locks that will block this
+        ** lock.  If any do, return SQLITE_BUSY right away.  */
+        for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
+          u16 mExcl = AtomicLoad(&pX->exclMask);
+          u16 mShared = AtomicLoad(&pX->sharedMask);
+          if( (mExcl|mShared) & mask ) rc = SQLITE_BUSY;
+        }
+  
+        /* Get the exclusive locks at the system level. If successful,
+        ** also update the in-memory values. */
+        if( rc==SQLITE_OK ){
+          rc = winShmSystemLock(pDbFd, WINSHM_WRLCK, ofst+WIN_SHM_BASE, n);
+          if( rc==SQLITE_OK ){
+            AtomicStore(&p->exclMask, p->exclMask | mask);
+          }
+        }
       }
     }
+
+    /* Release the mutex(es) taken at the top of this block */
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+    for(iMutex--; iMutex>=ofst; iMutex--){
+      sqlite3_mutex_leave(pShmNode->aMutex[iMutex]);
+    }
+#else
+    sqlite3_mutex_leave(pShmNode->mutex);
+#endif
   }
-  sqlite3_mutex_leave(pShmNode->mutex);
-  OSTRACE(("SHM-LOCK pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x, rc=%s\n",
-           osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask,
-           sqlite3ErrName(rc)));
+
+  OSTRACE((
+      "SHM-LOCK(%d,%d,%d) pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x, rc=%s\n",
+      ofst, n, flags, osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask,
+      sqlite3ErrName(rc)));
   return rc;
 }
 
@@ -4238,7 +4490,7 @@ static int winShmMap(
 
   sqlite3_mutex_enter(pShmNode->mutex);
   if( pShmNode->isUnlocked ){
-    rc = winLockSharedMemory(pShmNode);
+    rc = winLockSharedMemory(pDbFd);
     if( rc!=SQLITE_OK ) goto shmpage_out;
     pShmNode->isUnlocked = 0;
   }
index 9a2237e4c0e0e3f33fa9a7e1169906a1af933b13..3564e5c39962f90953b91c81ffd3e2b0d3cc088a 100644 (file)
@@ -37,10 +37,11 @@ proc canCreateWin32Symlink {} {
   set link [file join $::testdir lnk[pid].sym]
   if {[file exists $link]} { return 0 }
   set target [info nameofexecutable]
-  if {[catch {createWin32Symlink $link $target}] == 0} {
+  if {[catch {createWin32Symlink $link $target} msg] == 0} {
     deleteWin32Symlink $link
     return 1
   }
+  puts $msg
   return 0
 }
 
index 50988debe3cae77f67ffc7a8e7fbee2b3c8fe363..533e51c21bec09f5cd577fb63a1cafea9673bfb8 100644 (file)
@@ -506,7 +506,7 @@ do_multiclient_test tn {
   do_test wal-10.$tn.6 {
     sql3 {SELECT * FROM t1}
   } {1 2 3 4 5 6}
-  do_test wal-10.$tn.7 {
+  do_test wal-10.$tn.7a {
     sql2 COMMIT
   } {}
 
@@ -521,7 +521,7 @@ do_multiclient_test tn {
   # to the database (as it is not locked and [db] is reading the latest
   # snapshot).
   #
-  do_test wal-10.$tn.7 {
+  do_test wal-10.$tn.7b {
     sql2 { BEGIN; INSERT INTO t1 VALUES(7, 8) ; }
     catchsql { INSERT INTO t1 VALUES(9, 10) }
   } {1 {database is locked}}