From: dan Date: Fri, 22 Nov 2024 21:24:08 +0000 (+0000) Subject: Supports SQLITE_ENABLE_SETLK_TIMEOUT on windows. Does not work properly yet. X-Git-Tag: major-release~248^2~35 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6bd3faa09248a8ec70e706041afeac42610f443b;p=thirdparty%2Fsqlite.git Supports SQLITE_ENABLE_SETLK_TIMEOUT on windows. Does not work properly yet. FossilOrigin-Name: 737ca8a9fb9dc74b28f2186d93c5101463497445d0fabba3def61fee29abf2c8 --- diff --git a/Makefile.msc b/Makefile.msc index 32b8143768..c56f2d3051 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -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 ################################ ############################################################################### diff --git a/manifest b/manifest index a1f3f97ae3..1a7b05d77f 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index 8cd2bbe8d4..3a08657fec 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0fe1622cec95b7ebecc127ee57a08113d3da1dadbe72c03a13d6751b3043e50f +737ca8a9fb9dc74b28f2186d93c5101463497445d0fabba3def61fee29abf2c8 diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 7eb5b50be1..7b411018f5 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -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++; diff --git a/src/os_unix.c b/src/os_unix.c index b1996278c8..77855a8dd0 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -4290,7 +4290,6 @@ static int unixGetpagesize(void){ ** ** nRef ** -** The following fields are read-only after the object is created: ** ** hShm ** zFilename diff --git a/src/os_win.c b/src/os_win.c index 8ce1647f60..ff094e8d14 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -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; iiaMutex[ii]); + } +#else + sqlite3_mutex_enter(pShmNode->mutex); +#endif +} +static void winShmListMutexLeave(winShmNode *pShmNode){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int ii; + for(ii=0; iiaMutex[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; iaMutex[i]); + } +#endif for(i=0; inRegion; 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; iiaMutex[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<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<1 || mask==(1<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 && ofstexclMask|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<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; iMutexaMutex[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; } diff --git a/test/symlink2.test b/test/symlink2.test index 9a2237e4c0..3564e5c399 100644 --- a/test/symlink2.test +++ b/test/symlink2.test @@ -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 } diff --git a/test/wal.test b/test/wal.test index 50988debe3..533e51c21b 100644 --- a/test/wal.test +++ b/test/wal.test @@ -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}}