diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/btree.c | 15 | ||||
-rw-r--r-- | src/os_unix.c | 18 | ||||
-rw-r--r-- | src/pager.c | 16 | ||||
-rw-r--r-- | src/pager.h | 6 | ||||
-rw-r--r-- | src/wal.c | 114 | ||||
-rw-r--r-- | src/wal.h | 4 |
6 files changed, 126 insertions, 47 deletions
diff --git a/src/btree.c b/src/btree.c index 76845e072..3b8d0529c 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3437,6 +3437,18 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ pBt->btsFlags &= ~BTS_INITIALLY_EMPTY; if( pBt->nPage==0 ) pBt->btsFlags |= BTS_INITIALLY_EMPTY; do { + Pager *pPager = pBt->pPager; + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If transitioning from no transaction directly to a write transaction, + ** block for the WRITER lock first if possible. */ + if( pBt->pPage1==0 && wrflag ){ + assert( pBt->inTransaction==TRANS_NONE ); + rc = sqlite3PagerWalWriteLock(p->db, pPager, 1); + if( rc!=SQLITE_OK ) break; + } +#endif + /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after @@ -3450,7 +3462,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){ rc = SQLITE_READONLY; }else{ - rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db)); + rc = sqlite3PagerBegin(pPager, wrflag>1, sqlite3TempInMemory(p->db)); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); }else if( rc==SQLITE_BUSY_SNAPSHOT && pBt->inTransaction==TRANS_NONE ){ @@ -3463,6 +3475,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ } if( rc!=SQLITE_OK ){ + sqlite3PagerWalWriteLock(p->db, pPager, 0); unlockBtreeIfUnused(pBt); } }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && diff --git a/src/os_unix.c b/src/os_unix.c index 56e53929e..e1ed575b0 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -4819,22 +4819,24 @@ static int unixShmLock( assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 ); assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 ); - /* Check that, if this to be a blocking lock, that locks have been - ** obtained in the following order. + /* 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. Recover lock (ofst==2). + ** 2. Write lock (ofst==0). ** 3. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK). - ** 4. Write lock (ofst==0). ** ** 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. */ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT - assert( pDbFd->iBusyTimeout==0 - || (flags & SQLITE_SHM_UNLOCK) || ofst==0 - || ((p->exclMask|p->sharedMask)&~((1<<ofst)-2))==0 - ); + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2) /* not RECOVER */ + && (n==1) /* Single lock only */ + && (ofst!=1 || (p->exclMask|p->sharedMask)==0) + && (ofst!=0 || (p->exclMask|p->sharedMask)<3) + && (ofst<3 || (p->exclMask|p->sharedMask)<(1<<ofst)) + )); #endif mask = (1<<(ofst+n)) - (1<<ofst); diff --git a/src/pager.c b/src/pager.c index dd723b788..1570b3e70 100644 --- a/src/pager.c +++ b/src/pager.c @@ -7574,7 +7574,21 @@ int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } - +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +/* +** If pager pPager is a wal-mode database not in exclusive locking mode, +** invoke the sqlite3WalWriteLock() function on the associated Wal object +** with the same db and bLock parameters as were passed to this function. +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +int sqlite3PagerWalWriteLock(sqlite3 *db, Pager *pPager, int bLock){ + int rc = SQLITE_OK; + if( pagerUseWal(pPager) && pPager->exclusiveMode==0 ){ + rc = sqlite3WalWriteLock(db, pPager->pWal, bLock); + } + return rc; +} +#endif #ifdef SQLITE_ENABLE_SNAPSHOT /* diff --git a/src/pager.h b/src/pager.h index a044daa18..c203908c8 100644 --- a/src/pager.h +++ b/src/pager.h @@ -185,6 +185,12 @@ int sqlite3PagerSharedLock(Pager *pPager); # endif #endif +#if !defined(SQLITE_OMIT_WAL) && defined(SQLITE_ENABLE_SETLK_TIMEOUT) + int sqlite3PagerWalWriteLock(sqlite3*, Pager*, int); +#else +# define sqlite3PagerWalWriteLock(x,y,z) SQLITE_OK +#endif + #ifdef SQLITE_DIRECT_OVERFLOW_READ int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno); #endif @@ -2731,6 +2731,65 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ } #endif /* SQLITE_ENABLE_SNAPSHOT */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +/* +** Attempt to enable blocking locks. Blocking locks are enabled only if (a) +** they are supported by the VFS, and (b) the database handle is configured +** with a busy-timeout. Return 1 if blocking locks are successfully enabled, +** or 0 otherwise. +*/ +static int walEnableBlocking(sqlite3 *db, Wal *pWal){ + int res = 0; + if( db->busyTimeout ){ + int rc; + int tmout = db->busyTimeout; + rc = sqlite3OsFileControl( + pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout + ); + res = (rc==SQLITE_OK); + } + return res; +} + +/* +** Disable blocking locks. +*/ +static void walDisableBlocking(Wal *pWal){ + int tmout = 0; + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout); +} + +/* +** If parameter bLock is true, attempt to enable blocking locks, take +** the WRITER lock, and then disable blocking locks. If blocking locks +** cannot be enabled, no attempt to obtain the WRITER lock is made. Return +** an SQLite error code if an error occurs, or SQLITE_OK otherwise. It is not +** an error if blocking locks can not be enabled. +** +** If the bLock parameter is false and the WRITER lock is held, release it. +*/ +int sqlite3WalWriteLock(sqlite3 *db, Wal *pWal, int bLock){ + int rc = SQLITE_OK; + assert( pWal->readLock<0 || bLock==0 ); + if( bLock ){ + if( walEnableBlocking(db, pWal) ){ + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + } + walDisableBlocking(pWal); + } + }else if( pWal->writeLock ){ + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + pWal->writeLock = 0; + } + return rc; +} +#else +# define walEnableBlocking(x,y) 0 +# define walDisableBlocking(x) +#endif /* ifdef SQLITE_ENABLE_SETLK_TIMEOUT */ + /* ** Begin a read transaction on the database. ** @@ -2754,14 +2813,6 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; if( pSnapshot ){ -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - int busyTimeout = pWal->dbSnapshot->busyTimeout; - if( busyTimeout ){ - int tmout = busyTimeout; - sqlite3OsFileControl(pWal->pDbFd,SQLITE_FCNTL_LOCK_TIMEOUT,(void*)&tmout); - } -#endif - if( memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ bChanged = 1; } @@ -2774,14 +2825,9 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** its intent. To avoid the race condition this leads to, ensure that ** there is no checkpointer process by taking a shared CKPT lock ** before checking pInfo->nBackfillAttempted. */ + walEnableBlocking(pWal->dbSnapshot, pWal); rc = walLockShared(pWal, WAL_CKPT_LOCK); - -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - if( busyTimeout ){ - int tmout = 0; - sqlite3OsFileControl(pWal->pDbFd,SQLITE_FCNTL_LOCK_TIMEOUT,(void*)&tmout); - } -#endif + walDisableBlocking(pWal); if( rc!=SQLITE_OK ){ return rc; @@ -2861,8 +2907,8 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** read-lock. */ void sqlite3WalEndReadTransaction(Wal *pWal){ - sqlite3WalEndWriteTransaction(pWal); if( pWal->readLock>=0 ){ + sqlite3WalEndWriteTransaction(pWal); walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); pWal->readLock = -1; } @@ -3022,6 +3068,16 @@ Pgno sqlite3WalDbsize(Wal *pWal){ int sqlite3WalBeginWriteTransaction(Wal *pWal){ int rc; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If the write-lock is already held, then it was obtained before the + ** read-transaction was even opened, making this call a no-op. + ** Return early. */ + if( pWal->writeLock ){ + assert( !memcmp(&pWal->hdr,(void *)walIndexHdr(pWal),sizeof(WalIndexHdr)) ); + return SQLITE_OK; + } +#endif + /* Cannot start a write transaction without first holding a read ** transaction. */ assert( pWal->readLock>=0 ); @@ -3587,9 +3643,6 @@ int sqlite3WalCheckpoint( int isChanged = 0; /* True if a new wal-index header is loaded */ int eMode2 = eMode; /* Mode to pass to walCheckpoint() */ int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */ -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - int bSetLk = 0; -#endif assert( pWal->ckptLock==0 ); assert( pWal->writeLock==0 ); @@ -3601,18 +3654,11 @@ int sqlite3WalCheckpoint( if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - if( db->busyTimeout ){ - int tmout = db->busyTimeout; - sqlite3_file *fd = pWal->pDbFd; - if( SQLITE_OK== - sqlite3OsFileControl(fd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout) - ){ - xBusy2 = 0; - bSetLk = 1; - } + /* Enable blocking locks, if possible. If blocking locks are successfully + ** enabled, set xBusy2=0 so that the busy-handler is never invoked. */ + if( walEnableBlocking(db, pWal) ){ + xBusy2 = 0; } -#endif /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive ** "checkpoint" lock on the database file. @@ -3684,13 +3730,7 @@ int sqlite3WalCheckpoint( memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); } -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - if( bSetLk ){ - int tmout = 0; - sqlite3_file *fd = pWal->pDbFd; - sqlite3OsFileControl(fd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout); - } -#endif + walDisableBlocking(pWal); /* Release the locks. */ sqlite3WalEndWriteTransaction(pWal); @@ -146,5 +146,9 @@ int sqlite3WalFramesize(Wal *pWal); /* Return the sqlite3_file object for the WAL file */ sqlite3_file *sqlite3WalFile(Wal *pWal); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +int sqlite3WalWriteLock(sqlite3 *db, Wal *pWal, int bLock); +#endif + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* SQLITE_WAL_H */ |