mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
If the target database URI passed to sqlite3rbu_open() is contains the option "rbu_exclusive_checkpoint=1", hold an exclusive lock for the duration of any incremental checkpoint operation.
FossilOrigin-Name: 7cb77296a22a87e7ed4c3544792f0204f704f01f384590c32c256bec4517c9bc
This commit is contained in:
102
ext/rbu/rbuexlock.test
Normal file
102
ext/rbu/rbuexlock.test
Normal file
@ -0,0 +1,102 @@
|
||||
# 2021 November 06
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbuexlock
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
sqlite3_config_uri 1
|
||||
|
||||
# Create a simple RBU database. That expects to write to a table:
|
||||
#
|
||||
# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
#
|
||||
proc create_rbu {filename} {
|
||||
forcedelete $filename
|
||||
sqlite3 rbu1 $filename
|
||||
rbu1 eval {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(10, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(20, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(30, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(40, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(50, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(60, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(70, random(), random(), 0);
|
||||
INSERT INTO data_t1 VALUES(80, random(), random(), 0);
|
||||
}
|
||||
rbu1 close
|
||||
return $filename
|
||||
}
|
||||
|
||||
reset_db
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b INT, c INT);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
}
|
||||
create_rbu rbu1.db
|
||||
|
||||
do_test 1.1.0 {
|
||||
sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db
|
||||
rbu step
|
||||
} SQLITE_OK
|
||||
do_catchsql_test 1.1.1 { SELECT * FROM t1 } {0 {1 2 3}}
|
||||
|
||||
do_test 1.2.0 {
|
||||
for {set ii 0} {$ii < 10} {incr ii} {
|
||||
rbu step
|
||||
}
|
||||
rbu step
|
||||
} SQLITE_OK
|
||||
do_catchsql_test 1.2.1 { SELECT * FROM t1 } {0 {1 2 3}}
|
||||
|
||||
do_test 1.3.0 {
|
||||
while {[file exists test.db-wal]==0} {
|
||||
rbu step
|
||||
}
|
||||
} {}
|
||||
do_catchsql_test 1.3.1 { SELECT * FROM t1 } {1 {database is locked}}
|
||||
|
||||
do_test 1.4.0 {
|
||||
rbu step
|
||||
} SQLITE_OK
|
||||
do_catchsql_test 1.4.1 { SELECT * FROM t1 } {1 {database is locked}}
|
||||
|
||||
rbu close
|
||||
|
||||
do_test 1.5.0 {
|
||||
file exists test.db-wal
|
||||
} {1}
|
||||
do_test 1.5.1 {
|
||||
sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db
|
||||
file exists test.db-wal
|
||||
} 1
|
||||
do_catchsql_test 1.5.2 { SELECT * FROM t1 } {1 {database is locked}}
|
||||
|
||||
do_test 1.6.0 {
|
||||
rbu step
|
||||
} SQLITE_OK
|
||||
do_catchsql_test 1.6.1 { SELECT * FROM t1 } {1 {database is locked}}
|
||||
|
||||
do_test 1.7.0 {
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
rbu close
|
||||
} SQLITE_DONE
|
||||
do_catchsql_test 1.7.2 { SELECT count(*) FROM t1 } {0 9}
|
||||
|
||||
finish_test
|
||||
|
||||
|
@ -110,6 +110,13 @@
|
||||
# define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Name of the URI option that causes RBU to take an exclusive lock as
|
||||
** part of the incremental checkpoint operation.
|
||||
*/
|
||||
#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint"
|
||||
|
||||
|
||||
/*
|
||||
** The rbu_state table is used to save the state of a partially applied
|
||||
** update so that it can be resumed later. The table consists of integer
|
||||
@ -2756,13 +2763,19 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
|
||||
/*
|
||||
** Open the database handle and attach the RBU database as "rbu". If an
|
||||
** error occurs, leave an error code and message in the RBU handle.
|
||||
**
|
||||
** If argument dbMain is not NULL, then it is a database handle already
|
||||
** open on the target database. Use this handle instead of opening a new
|
||||
** one.
|
||||
*/
|
||||
static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){
|
||||
static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){
|
||||
assert( p->rc || (p->dbMain==0 && p->dbRbu==0) );
|
||||
assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 );
|
||||
assert( dbMain==0 || rbuIsVacuum(p)==0 );
|
||||
|
||||
/* Open the RBU database */
|
||||
p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1);
|
||||
p->dbMain = dbMain;
|
||||
|
||||
if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){
|
||||
sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
|
||||
@ -3128,15 +3141,31 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){
|
||||
|
||||
|
||||
/*
|
||||
** Take an EXCLUSIVE lock on the database file.
|
||||
** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if
|
||||
** successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
static void rbuLockDatabase(sqlite3rbu *p){
|
||||
sqlite3_file *pReal = p->pTargetFd->pReal;
|
||||
assert( p->rc==SQLITE_OK );
|
||||
p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_EXCLUSIVE);
|
||||
static int rbuLockDatabase(sqlite3 *db){
|
||||
int rc = SQLITE_OK;
|
||||
sqlite3_file *fd = 0;
|
||||
sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
|
||||
|
||||
if( fd->pMethods ){
|
||||
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the database handle passed as the only argument
|
||||
** was opened with the rbu_exclusive_checkpoint=1 URI parameter
|
||||
** specified. Or false otherwise.
|
||||
*/
|
||||
static int rbuExclusiveCheckpoint(sqlite3 *db){
|
||||
const char *zUri = sqlite3_db_filename(db, 0);
|
||||
return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0);
|
||||
}
|
||||
|
||||
#if defined(_WIN32_WCE)
|
||||
@ -3194,18 +3223,24 @@ static void rbuMoveOalFile(sqlite3rbu *p){
|
||||
** In order to ensure that there are no database readers, an EXCLUSIVE
|
||||
** lock is obtained here before the *-oal is moved to *-wal.
|
||||
*/
|
||||
rbuLockDatabase(p);
|
||||
sqlite3 *dbMain = 0;
|
||||
rbuFileSuffix3(zBase, zWal);
|
||||
rbuFileSuffix3(zBase, zOal);
|
||||
|
||||
/* Re-open the databases. */
|
||||
rbuObjIterFinalize(&p->objiter);
|
||||
sqlite3_close(p->dbRbu);
|
||||
sqlite3_close(p->dbMain);
|
||||
p->dbMain = 0;
|
||||
p->dbRbu = 0;
|
||||
|
||||
dbMain = rbuOpenDbhandle(p, p->zTarget, 1);
|
||||
if( dbMain ){
|
||||
assert( p->rc==SQLITE_OK );
|
||||
p->rc = rbuLockDatabase(dbMain);
|
||||
}
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
rbuFileSuffix3(zBase, zWal);
|
||||
rbuFileSuffix3(zBase, zOal);
|
||||
|
||||
/* Re-open the databases. */
|
||||
rbuObjIterFinalize(&p->objiter);
|
||||
sqlite3_close(p->dbRbu);
|
||||
sqlite3_close(p->dbMain);
|
||||
p->dbMain = 0;
|
||||
p->dbRbu = 0;
|
||||
|
||||
#if defined(_WIN32_WCE)
|
||||
{
|
||||
LPWSTR zWideOal;
|
||||
@ -3232,11 +3267,19 @@ static void rbuMoveOalFile(sqlite3rbu *p){
|
||||
#else
|
||||
p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
rbuOpenDatabase(p, 0);
|
||||
rbuSetupCheckpoint(p, 0);
|
||||
}
|
||||
if( p->rc!=SQLITE_OK
|
||||
|| rbuIsVacuum(p)
|
||||
|| rbuExclusiveCheckpoint(dbMain)==0
|
||||
){
|
||||
sqlite3_close(dbMain);
|
||||
dbMain = 0;
|
||||
}
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
rbuOpenDatabase(p, dbMain, 0);
|
||||
rbuSetupCheckpoint(p, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3987,9 +4030,9 @@ static sqlite3rbu *openRbuHandle(
|
||||
** If this is the case, it will have been checkpointed and deleted
|
||||
** when the handle was closed and a second attempt to open the
|
||||
** database may succeed. */
|
||||
rbuOpenDatabase(p, &bRetry);
|
||||
rbuOpenDatabase(p, 0, &bRetry);
|
||||
if( bRetry ){
|
||||
rbuOpenDatabase(p, 0);
|
||||
rbuOpenDatabase(p, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4084,6 +4127,14 @@ static sqlite3rbu *openRbuHandle(
|
||||
}else if( p->eStage==RBU_STAGE_MOVE ){
|
||||
/* no-op */
|
||||
}else if( p->eStage==RBU_STAGE_CKPT ){
|
||||
if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){
|
||||
/* If the rbu_exclusive_checkpoint=1 URI parameter was specified
|
||||
** and an incremental checkpoint is being resumed, attempt an
|
||||
** exclusive lock on the db file. If this fails, so be it. */
|
||||
p->eStage = RBU_STAGE_DONE;
|
||||
rbuLockDatabase(p->dbMain);
|
||||
p->eStage = RBU_STAGE_CKPT;
|
||||
}
|
||||
rbuSetupCheckpoint(p, pState);
|
||||
}else if( p->eStage==RBU_STAGE_DONE ){
|
||||
p->rc = SQLITE_DONE;
|
||||
@ -4121,7 +4172,6 @@ sqlite3rbu *sqlite3rbu_open(
|
||||
const char *zState
|
||||
){
|
||||
if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); }
|
||||
/* TODO: Check that zTarget and zRbu are non-NULL */
|
||||
return openRbuHandle(zTarget, zRbu, zState);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user