mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-21 09:00:59 +03:00
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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
75
src/vdbe.c
75
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user