diff options
author | danielk1977 <danielk1977@noemail.net> | 2009-03-23 17:11:26 +0000 |
---|---|---|
committer | danielk1977 <danielk1977@noemail.net> | 2009-03-23 17:11:26 +0000 |
commit | a8bbef84bf3214905ceb9ea32fc249010dbde41a (patch) | |
tree | 14594a477429fb069f01068b39d30e208ffde5f0 /src | |
parent | bc73971db6162bf60d775775b180cea06615b29c (diff) | |
download | sqlite-a8bbef84bf3214905ceb9ea32fc249010dbde41a.tar.gz sqlite-a8bbef84bf3214905ceb9ea32fc249010dbde41a.zip |
Fix an obscure race condition that can occur when multiple threads, shared cache and DDL statements are combined. Enhance notify2.test to test this scenario. (CVS 6373)
FossilOrigin-Name: 92ec5975123284aff3a69ee16c397d9e2a844c0b
Diffstat (limited to 'src')
-rw-r--r-- | src/prepare.c | 4 | ||||
-rw-r--r-- | src/test_thread.c | 23 | ||||
-rw-r--r-- | src/vdbe.c | 75 |
3 files changed, 69 insertions, 33 deletions
diff --git a/src/prepare.c b/src/prepare.c index 3367f7979..282e46a84 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -13,7 +13,7 @@ ** interface, and routines that contribute to loading the database schema ** from disk. ** -** $Id: prepare.c,v 1.111 2009/03/19 18:51:07 danielk1977 Exp $ +** $Id: prepare.c,v 1.112 2009/03/23 17:11:27 danielk1977 Exp $ */ #include "sqliteInt.h" @@ -91,7 +91,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ pData->rc = rc; if( rc==SQLITE_NOMEM ){ db->mallocFailed = 1; - }else if( rc!=SQLITE_INTERRUPT ){ + }else if( rc!=SQLITE_INTERRUPT && (rc&0xff)!=SQLITE_LOCKED ){ corruptSchema(pData, argv[0], zErr); } sqlite3DbFree(db, zErr); diff --git a/src/test_thread.c b/src/test_thread.c index 20fcf73a2..ce51a6e05 100644 --- a/src/test_thread.c +++ b/src/test_thread.c @@ -14,7 +14,7 @@ ** test that sqlite3 database handles may be concurrently accessed by ** multiple threads. Right now this only works on unix. ** -** $Id: test_thread.c,v 1.12 2009/03/19 07:58:31 danielk1977 Exp $ +** $Id: test_thread.c,v 1.13 2009/03/23 17:11:27 danielk1977 Exp $ */ #include "sqliteInt.h" @@ -117,8 +117,10 @@ static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){ Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0); #if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); - Tcl_CreateObjCommand( - interp, "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc,0,0); + Tcl_CreateObjCommand(interp, + "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); + Tcl_CreateObjCommand(interp, + "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); #endif Sqlitetest1_Init(interp); Sqlitetest_mutex_Init(interp); @@ -544,6 +546,7 @@ static int blocking_step_proc( /* ** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar? +** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar? */ static int blocking_prepare_v2_proc( void * clientData, @@ -558,6 +561,7 @@ static int blocking_prepare_v2_proc( sqlite3_stmt *pStmt = 0; char zBuf[50]; int rc; + int isBlocking = !(clientData==0); if( objc!=5 && objc!=4 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", @@ -568,7 +572,12 @@ static int blocking_prepare_v2_proc( zSql = Tcl_GetString(objv[2]); if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR; - rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, objc>=5?&zTail : 0); + if( isBlocking ){ + rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail); + }else{ + rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail); + } + assert(rc==SQLITE_OK || pStmt==0); if( zTail && objc>=5 ){ if( bytes>=0 ){ @@ -603,8 +612,10 @@ int SqlitetestThread_Init(Tcl_Interp *interp){ Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); #if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); - Tcl_CreateObjCommand( - interp, "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc,0,0); + Tcl_CreateObjCommand(interp, + "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); + Tcl_CreateObjCommand(interp, + "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); #endif return TCL_OK; } diff --git a/src/vdbe.c b/src/vdbe.c index 97f5ae696..128e81bdf 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -43,7 +43,7 @@ ** in this file for details. If in doubt, do not deviate from existing ** commenting and indentation practices when changing or adding code. ** -** $Id: vdbe.c,v 1.827 2009/03/18 10:33:02 danielk1977 Exp $ +** $Id: vdbe.c,v 1.828 2009/03/23 17:11:27 danielk1977 Exp $ */ #include "sqliteInt.h" #include "vdbeInt.h" @@ -4371,33 +4371,58 @@ case OP_CreateTable: { /* out2-prerelease */ ** then runs the new virtual machine. It is thus a re-entrant opcode. */ case OP_ParseSchema: { - char *zSql; int iDb = pOp->p1; - const char *zMaster; - InitData initData; - assert( iDb>=0 && iDb<db->nDb ); - if( !pOp->p2 && !DbHasProperty(db, iDb, DB_SchemaLoaded) ){ - break; + + /* If pOp->p2 is 0, then this opcode is being executed to read a + ** single row, for example the row corresponding to a new index + ** created by this VDBE, from the sqlite_master table. It only + ** does this if the corresponding in-memory schema is currently + ** loaded. Otherwise, the new index definition can be loaded along + ** with the rest of the schema when it is required. + ** + ** Although the mutex on the BtShared object that corresponds to + ** database iDb (the database containing the sqlite_master table + ** read by this instruction) is currently held, it is necessary to + ** obtain the mutexes on all attached databases before checking if + ** the schema of iDb is loaded. This is because, at the start of + ** the sqlite3_exec() call below, SQLite will invoke + ** sqlite3BtreeEnterAll(). If all mutexes are not already held, the + ** iDb mutex may be temporarily released to avoid deadlock. If + ** this happens, then some other thread may delete the in-memory + ** schema of database iDb before the SQL statement runs. The schema + ** will not be reloaded becuase the db->init.busy flag is set. This + ** can result in a "no such table: sqlite_master" or "malformed + ** database schema" error being returned to the user. + */ + assert( sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); + sqlite3BtreeEnterAll(db); + if( pOp->p2 || DbHasProperty(db, iDb, DB_SchemaLoaded) ){ + const char *zMaster = SCHEMA_TABLE(iDb); + char *zSql; + InitData initData; + initData.db = db; + initData.iDb = pOp->p1; + initData.pzErrMsg = &p->zErrMsg; + zSql = sqlite3MPrintf(db, + "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s", + db->aDb[iDb].zName, zMaster, pOp->p4.z); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + (void)sqlite3SafetyOff(db); + assert( db->init.busy==0 ); + db->init.busy = 1; + initData.rc = SQLITE_OK; + assert( !db->mallocFailed ); + rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); + if( rc==SQLITE_OK ) rc = initData.rc; + sqlite3DbFree(db, zSql); + db->init.busy = 0; + (void)sqlite3SafetyOn(db); + } } - zMaster = SCHEMA_TABLE(iDb); - initData.db = db; - initData.iDb = pOp->p1; - initData.pzErrMsg = &p->zErrMsg; - zSql = sqlite3MPrintf(db, - "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s", - db->aDb[iDb].zName, zMaster, pOp->p4.z); - if( zSql==0 ) goto no_mem; - (void)sqlite3SafetyOff(db); - assert( db->init.busy==0 ); - db->init.busy = 1; - initData.rc = SQLITE_OK; - assert( !db->mallocFailed ); - rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); - if( rc==SQLITE_OK ) rc = initData.rc; - sqlite3DbFree(db, zSql); - db->init.busy = 0; - (void)sqlite3SafetyOn(db); + sqlite3BtreeLeaveAll(db); if( rc==SQLITE_NOMEM ){ goto no_mem; } |