1
0
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:
dan
2021-11-05 19:04:01 +00:00
parent 695c87f67d
commit 1182e2ac7d
4 changed files with 186 additions and 33 deletions

102
ext/rbu/rbuexlock.test Normal file
View 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

View File

@ -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);
}