diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mutex_w32.c | 16 | ||||
-rw-r--r-- | src/os_unix.c | 1 | ||||
-rw-r--r-- | src/os_win.c | 450 |
3 files changed, 354 insertions, 113 deletions
diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 7eb5b50be..7b411018f 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 b1996278c..77855a8dd 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 8ce1647f6..ff094e8d1 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 /* @@ -2541,6 +2547,73 @@ static BOOL winLockFile( } /* +** 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. */ static BOOL winUnlockFile( @@ -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; } |