aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/btree.c16
-rw-r--r--src/main.c46
-rw-r--r--src/os_unix.c18
-rw-r--r--src/pager.c36
-rw-r--r--src/pager.h15
-rw-r--r--src/sqliteInt.h1
-rw-r--r--src/wal.c264
-rw-r--r--src/wal.h6
8 files changed, 232 insertions, 170 deletions
diff --git a/src/btree.c b/src/btree.c
index 0e1f7a2dc..e80add6eb 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,11 +3475,11 @@ 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 &&
btreeInvokeBusyHandler(pBt) );
- sqlite3PagerResetLockTimeout(pBt->pPager);
if( rc==SQLITE_OK ){
if( p->inTrans==TRANS_NONE ){
diff --git a/src/main.c b/src/main.c
index 778d4324b..e96f5e113 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1614,8 +1614,7 @@ const char *sqlite3ErrStr(int rc){
*/
static int sqliteDefaultBusyCallback(
void *ptr, /* Database connection */
- int count, /* Number of times table has been busy */
- sqlite3_file *pFile /* The file on which the lock occurred */
+ int count /* Number of times table has been busy */
){
#if SQLITE_OS_WIN || HAVE_USLEEP
/* This case is for systems that have support for sleeping for fractions of
@@ -1629,31 +1628,6 @@ static int sqliteDefaultBusyCallback(
int tmout = db->busyTimeout;
int delay, prior;
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- if( sqlite3OsFileControl(pFile,SQLITE_FCNTL_LOCK_TIMEOUT,&tmout)==SQLITE_OK ){
- if( count ){
- /* If this is the second or later invocation of the busy-handler,
- ** but tmout==0, then code in wal.c must have disabled the blocking
- ** lock before the SQLITE_BUSY error was hit. In this case, no delay
- ** occurred while waiting for the lock, so fall through to the xSleep()
- ** code below to delay a while before retrying the lock.
- **
- ** Alternatively, if tmout!=0, then SQLite has already waited
- ** sqlite3.busyTimeout ms for a lock. In this case, return 0 to
- ** indicate that the lock should not be retried and the SQLITE_BUSY
- ** error returned to the application. */
- if( tmout ){
- tmout = 0;
- sqlite3OsFileControl(pFile, SQLITE_FCNTL_LOCK_TIMEOUT, &tmout);
- return 0;
- }
- }else{
- return 1;
- }
- }
-#else
- UNUSED_PARAMETER(pFile);
-#endif
assert( count>=0 );
if( count < NDELAY ){
delay = delays[count];
@@ -1673,7 +1647,6 @@ static int sqliteDefaultBusyCallback(
** must be done in increments of whole seconds */
sqlite3 *db = (sqlite3 *)ptr;
int tmout = ((sqlite3 *)ptr)->busyTimeout;
- UNUSED_PARAMETER(pFile);
if( (count+1)*1000 > tmout ){
return 0;
}
@@ -1694,16 +1667,7 @@ static int sqliteDefaultBusyCallback(
int sqlite3InvokeBusyHandler(BusyHandler *p, sqlite3_file *pFile){
int rc;
if( p->xBusyHandler==0 || p->nBusy<0 ) return 0;
- if( p->bExtraFileArg ){
- /* Add an extra parameter with the pFile pointer to the end of the
- ** callback argument list */
- int (*xTra)(void*,int,sqlite3_file*);
- xTra = (int(*)(void*,int,sqlite3_file*))p->xBusyHandler;
- rc = xTra(p->pBusyArg, p->nBusy, pFile);
- }else{
- /* Legacy style busy handler callback */
- rc = p->xBusyHandler(p->pBusyArg, p->nBusy);
- }
+ rc = p->xBusyHandler(p->pBusyArg, p->nBusy);
if( rc==0 ){
p->nBusy = -1;
}else{
@@ -1728,7 +1692,6 @@ int sqlite3_busy_handler(
db->busyHandler.xBusyHandler = xBusy;
db->busyHandler.pBusyArg = pArg;
db->busyHandler.nBusy = 0;
- db->busyHandler.bExtraFileArg = 0;
db->busyTimeout = 0;
sqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
@@ -1779,7 +1742,6 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){
sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback,
(void*)db);
db->busyTimeout = ms;
- db->busyHandler.bExtraFileArg = 1;
}else{
sqlite3_busy_handler(db, 0, 0);
}
@@ -4519,11 +4481,11 @@ int sqlite3_snapshot_open(
rc = SQLITE_OK;
}
if( rc==SQLITE_OK ){
- rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot);
+ rc = sqlite3PagerSnapshotOpen(pPager, db, pSnapshot);
}
if( rc==SQLITE_OK ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
- sqlite3PagerSnapshotOpen(pPager, 0);
+ sqlite3PagerSnapshotOpen(pPager, 0, 0);
}
if( bUnlock ){
sqlite3PagerSnapshotUnlock(pPager);
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 0d08a2dfa..1570b3e70 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -5705,7 +5705,6 @@ void sqlite3PagerUnrefPageOne(DbPage *pPg){
assert( pPg->pgno==1 );
assert( (pPg->flags & PGHDR_MMAP)==0 ); /* Page1 is never memory mapped */
pPager = pPg->pPager;
- sqlite3PagerResetLockTimeout(pPager);
sqlite3PcacheRelease(pPg);
pagerUnlockIfUnused(pPager);
}
@@ -6998,16 +6997,6 @@ sqlite3_file *sqlite3PagerFile(Pager *pPager){
return pPager->fd;
}
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
-/*
-** Reset the lock timeout for pager.
-*/
-void sqlite3PagerResetLockTimeout(Pager *pPager){
- int x = 0;
- sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_LOCK_TIMEOUT, &x);
-}
-#endif
-
/*
** Return the file handle for the journal file (if it exists).
** This will be either the rollback journal or the WAL file.
@@ -7421,7 +7410,6 @@ int sqlite3PagerCheckpoint(
pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
pnLog, pnCkpt
);
- sqlite3PagerResetLockTimeout(pPager);
}
return rc;
}
@@ -7586,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
/*
@@ -7606,10 +7608,14 @@ int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot){
** read transaction is opened, attempt to read from the snapshot it
** identifies. If this is not a WAL database, return an error.
*/
-int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){
+int sqlite3PagerSnapshotOpen(
+ Pager *pPager,
+ sqlite3 *db,
+ sqlite3_snapshot *pSnapshot
+){
int rc = SQLITE_OK;
if( pPager->pWal ){
- sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
+ sqlite3WalSnapshotOpen(pPager->pWal, db, pSnapshot);
}else{
rc = SQLITE_ERROR;
}
diff --git a/src/pager.h b/src/pager.h
index 2c99d67a9..c203908c8 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -177,14 +177,20 @@ int sqlite3PagerSharedLock(Pager *pPager);
int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
int sqlite3PagerCloseWal(Pager *pPager, sqlite3*);
# ifdef SQLITE_ENABLE_SNAPSHOT
- int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
- int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
+ int sqlite3PagerSnapshotGet(Pager*, sqlite3_snapshot **ppSnapshot);
+ int sqlite3PagerSnapshotOpen(Pager*, sqlite3*, sqlite3_snapshot *pSnapshot);
int sqlite3PagerSnapshotRecover(Pager *pPager);
int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot);
void sqlite3PagerSnapshotUnlock(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
@@ -210,11 +216,6 @@ int sqlite3PagerIsMemdb(Pager*);
void sqlite3PagerCacheStat(Pager *, int, int, int *);
void sqlite3PagerClearCache(Pager*);
int sqlite3SectorSize(sqlite3_file *);
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
-void sqlite3PagerResetLockTimeout(Pager *pPager);
-#else
-# define sqlite3PagerResetLockTimeout(X)
-#endif
/* Functions used to truncate the database file. */
void sqlite3PagerTruncateImage(Pager*,Pgno);
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 4a78d0cfd..b01f32393 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -994,7 +994,6 @@ struct BusyHandler {
int (*xBusyHandler)(void *,int); /* The busy callback */
void *pBusyArg; /* First arg to busy callback */
int nBusy; /* Incremented with each busy call */
- u8 bExtraFileArg; /* Include sqlite3_file as callback arg */
};
/*
diff --git a/src/wal.c b/src/wal.c
index b23085859..a70a71624 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -466,6 +466,9 @@ struct Wal {
#endif
#ifdef SQLITE_ENABLE_SNAPSHOT
WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */
+# ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ sqlite3 *dbSnapshot;
+# endif
#endif
};
@@ -1128,11 +1131,6 @@ static int walIndexRecover(Wal *pWal){
u32 aFrameCksum[2] = {0, 0};
int iLock; /* Lock offset to lock for checkpoint */
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- int tmout = 0;
- sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout);
-#endif
-
/* Obtain an exclusive lock on all byte in the locking range not already
** locked by the caller. The caller is guaranteed to have locked the
** WAL_WRITE_LOCK byte, and may have also locked the WAL_CKPT_LOCK byte.
@@ -2733,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.
**
@@ -2750,22 +2807,32 @@ int sqlite3WalSnapshotRecover(Wal *pWal){
int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
int rc; /* Return code */
int cnt = 0; /* Number of TryBeginRead attempts */
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- int tmout = 0;
-#endif
+
#ifdef SQLITE_ENABLE_SNAPSHOT
int bChanged = 0;
WalIndexHdr *pSnapshot = pWal->pSnapshot;
- if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
- bChanged = 1;
- }
-#endif
+ if( pSnapshot ){
+ if( memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
+ bChanged = 1;
+ }
+
+ /* It is possible that there is a checkpointer thread running
+ ** concurrent with this code. If this is the case, it may be that the
+ ** checkpointer has already determined that it will checkpoint
+ ** snapshot X, where X is later in the wal file than pSnapshot, but
+ ** has not yet set the pInfo->nBackfillAttempted variable to indicate
+ ** 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);
+ walDisableBlocking(pWal);
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- /* Disable blocking locks. They are not useful when trying to open a
- ** read-transaction, and blocking may cause deadlock anyway. */
- sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
#endif
do{
@@ -2776,16 +2843,6 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- /* If they were disabled earlier and the read-transaction has been
- ** successfully opened, re-enable blocking locks. This is because the
- ** connection may attempt to upgrade to a write-transaction, which does
- ** benefit from using blocking locks. */
- if( rc==SQLITE_OK ){
- sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout);
- }
-#endif
-
#ifdef SQLITE_ENABLE_SNAPSHOT
if( rc==SQLITE_OK ){
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
@@ -2807,48 +2864,40 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 );
assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame );
- /* It is possible that there is a checkpointer thread running
- ** concurrent with this code. If this is the case, it may be that the
- ** checkpointer has already determined that it will checkpoint
- ** snapshot X, where X is later in the wal file than pSnapshot, but
- ** has not yet set the pInfo->nBackfillAttempted variable to indicate
- ** 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.
- **
- ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing
- ** this already?
- */
- rc = walLockShared(pWal, WAL_CKPT_LOCK);
-
- if( rc==SQLITE_OK ){
- /* Check that the wal file has not been wrapped. Assuming that it has
- ** not, also check that no checkpointer has attempted to checkpoint any
- ** frames beyond pSnapshot->mxFrame. If either of these conditions are
- ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr
- ** with *pSnapshot and set *pChanged as appropriate for opening the
- ** snapshot. */
- if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
- && pSnapshot->mxFrame>=pInfo->nBackfillAttempted
- ){
- assert( pWal->readLock>0 );
- memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr));
- *pChanged = bChanged;
- }else{
- rc = SQLITE_ERROR_SNAPSHOT;
- }
-
- /* Release the shared CKPT lock obtained above. */
- walUnlockShared(pWal, WAL_CKPT_LOCK);
- pWal->minFrame = 1;
+ /* Check that the wal file has not been wrapped. Assuming that it has
+ ** not, also check that no checkpointer has attempted to checkpoint any
+ ** frames beyond pSnapshot->mxFrame. If either of these conditions are
+ ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr
+ ** with *pSnapshot and set *pChanged as appropriate for opening the
+ ** snapshot. */
+ if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
+ && pSnapshot->mxFrame>=pInfo->nBackfillAttempted
+ ){
+ assert( pWal->readLock>0 );
+ memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr));
+ *pChanged = bChanged;
+ }else{
+ rc = SQLITE_ERROR_SNAPSHOT;
}
+ /* A client using a non-current snapshot may not ignore any frames
+ ** from the start of the wal file. This is because, for a system
+ ** where (minFrame < iSnapshot < maxFrame), a checkpointer may
+ ** have omitted to checkpoint a frame earlier than minFrame in
+ ** the file because there exists a frame after iSnapshot that
+ ** is the same database page. */
+ pWal->minFrame = 1;
if( rc!=SQLITE_OK ){
sqlite3WalEndReadTransaction(pWal);
}
}
}
+
+ /* Release the shared CKPT lock obtained above. */
+ if( pSnapshot ){
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+ }
#endif
return rc;
}
@@ -2858,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;
}
@@ -3019,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 );
@@ -3595,42 +3654,48 @@ int sqlite3WalCheckpoint(
if( pWal->readOnly ) return SQLITE_READONLY;
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
- /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive
- ** "checkpoint" lock on the database file. */
- rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
- if( rc ){
- /* EVIDENCE-OF: R-10421-19736 If any other process is running a
- ** checkpoint operation at the same time, the lock cannot be obtained and
- ** SQLITE_BUSY is returned.
- ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
- ** it will not be invoked in this case.
- */
- testcase( rc==SQLITE_BUSY );
- testcase( xBusy!=0 );
- return rc;
+ /* 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;
}
- pWal->ckptLock = 1;
- /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
- ** TRUNCATE modes also obtain the exclusive "writer" lock on the database
- ** file.
- **
- ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
- ** immediately, and a busy-handler is configured, it is invoked and the
- ** writer lock retried until either the busy-handler returns 0 or the
- ** lock is successfully obtained.
+ /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive
+ ** "checkpoint" lock on the database file.
+ ** EVIDENCE-OF: R-10421-19736 If any other process is running a
+ ** checkpoint operation at the same time, the lock cannot be obtained and
+ ** SQLITE_BUSY is returned.
+ ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
+ ** it will not be invoked in this case.
*/
- if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
- rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
- if( rc==SQLITE_OK ){
- pWal->writeLock = 1;
- }else if( rc==SQLITE_BUSY ){
- eMode2 = SQLITE_CHECKPOINT_PASSIVE;
- xBusy2 = 0;
- rc = SQLITE_OK;
+ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ testcase( rc==SQLITE_BUSY );
+ testcase( rc!=SQLITE_OK && xBusy2!=0 );
+ if( rc==SQLITE_OK ){
+ pWal->ckptLock = 1;
+
+ /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
+ ** TRUNCATE modes also obtain the exclusive "writer" lock on the database
+ ** file.
+ **
+ ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
+ ** immediately, and a busy-handler is configured, it is invoked and the
+ ** writer lock retried until either the busy-handler returns 0 or the
+ ** lock is successfully obtained.
+ */
+ if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
+ rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1);
+ if( rc==SQLITE_OK ){
+ pWal->writeLock = 1;
+ }else if( rc==SQLITE_BUSY ){
+ eMode2 = SQLITE_CHECKPOINT_PASSIVE;
+ xBusy2 = 0;
+ rc = SQLITE_OK;
+ }
}
}
+
/* Read the wal-index header. */
if( rc==SQLITE_OK ){
rc = walIndexReadHdr(pWal, &isChanged);
@@ -3665,10 +3730,14 @@ int sqlite3WalCheckpoint(
memset(&pWal->hdr, 0, sizeof(WalIndexHdr));
}
+ walDisableBlocking(pWal);
+
/* Release the locks. */
sqlite3WalEndWriteTransaction(pWal);
- walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
- pWal->ckptLock = 0;
+ if( pWal->ckptLock ){
+ walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ pWal->ckptLock = 0;
+ }
WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok"));
return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc);
}
@@ -3786,8 +3855,15 @@ int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
/* Try to open on pSnapshot when the next read-transaction starts
*/
-void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){
+void sqlite3WalSnapshotOpen(
+ Wal *pWal,
+ sqlite3 *db,
+ sqlite3_snapshot *pSnapshot
+){
pWal->pSnapshot = (WalIndexHdr*)pSnapshot;
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ pWal->dbSnapshot = db;
+#endif
}
/*
diff --git a/src/wal.h b/src/wal.h
index 161060748..99ebc937a 100644
--- a/src/wal.h
+++ b/src/wal.h
@@ -130,7 +130,7 @@ int sqlite3WalHeapMemory(Wal *pWal);
#ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
-void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
+void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3*, sqlite3_snapshot *pSnapshot);
int sqlite3WalSnapshotRecover(Wal *pWal);
int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
void sqlite3WalSnapshotUnlock(Wal *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 */