mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-05 15:55:57 +03:00
Changes to avoid deadlock in SQLITE_ENABLE_SETLK_TIMEOUT builds.
FossilOrigin-Name: 553423c23142cf0ec219192315d57ce8a0e10c3d8678d28bc110a1a9a7c17cee
This commit is contained in:
222
src/wal.c
222
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.
|
||||
@@ -2750,22 +2748,45 @@ 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
rc = walLockShared(pWal, WAL_CKPT_LOCK);
|
||||
|
||||
#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( busyTimeout ){
|
||||
int tmout = 0;
|
||||
sqlite3OsFileControl(pWal->pDbFd,SQLITE_FCNTL_LOCK_TIMEOUT,(void*)&tmout);
|
||||
}
|
||||
#endif
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
do{
|
||||
@@ -2776,16 +2797,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 +2818,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;
|
||||
}
|
||||
@@ -3584,6 +3587,9 @@ 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 );
|
||||
@@ -3595,41 +3601,54 @@ 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;
|
||||
}
|
||||
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, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->writeLock = 1;
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
eMode2 = SQLITE_CHECKPOINT_PASSIVE;
|
||||
#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;
|
||||
rc = SQLITE_OK;
|
||||
bSetLk = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
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 ){
|
||||
@@ -3665,10 +3684,20 @@ 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
|
||||
|
||||
/* 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 +3815,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
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user