aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordrh <drh@noemail.net>2007-08-28 02:27:51 +0000
committerdrh <drh@noemail.net>2007-08-28 02:27:51 +0000
commit900b31ef49b4faed9c3f991ac1062f662f56a76d (patch)
treeceea21886b57fb93bdbb507e5da1b23780af34cf /src
parentda57895fec0ee37620a3dd2abff2fa8b35fb9fa7 (diff)
downloadsqlite-900b31ef49b4faed9c3f991ac1062f662f56a76d.tar.gz
sqlite-900b31ef49b4faed9c3f991ac1062f662f56a76d.zip
Work toward correct btree locking in a multithreaded environment. (CVS 4307)
FossilOrigin-Name: b8cc493b47e618648f645ab73eb0253739e03fcd
Diffstat (limited to 'src')
-rw-r--r--src/btmutex.c199
-rw-r--r--src/btree.c99
-rw-r--r--src/btree.h26
-rw-r--r--src/build.c3
-rw-r--r--src/sqliteInt.h4
-rw-r--r--src/vdbe.c36
-rw-r--r--src/vdbe.h3
-rw-r--r--src/vdbeInt.h3
-rw-r--r--src/vdbeaux.c7
9 files changed, 263 insertions, 117 deletions
diff --git a/src/btmutex.c b/src/btmutex.c
new file mode 100644
index 000000000..4725910f2
--- /dev/null
+++ b/src/btmutex.c
@@ -0,0 +1,199 @@
+/*
+** 2007 August 27
+**
+** 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.
+**
+*************************************************************************
+**
+** $Id: btmutex.c,v 1.1 2007/08/28 02:27:52 drh Exp $
+**
+** This file contains code used to implement mutexes on Btree objects.
+** This code really belongs in btree.c. But btree.c is getting too
+** big and we want to break it down some. This packaged seemed like
+** a good breakout.
+*/
+#include "btreeInt.h"
+#if SQLITE_THREADSAFE && !defined(SQLITE_OMIT_SHARED_CACHE)
+
+
+/*
+** Enter a mutex on the given BTree object.
+**
+** If the object is not sharable, then no mutex is ever required
+** and this routine is a no-op. The underlying mutex is non-recursive.
+** But we keep a reference count in Btree.wantToLock so the behavior
+** of this interface is recursive.
+**
+** To avoid deadlocks, multiple Btrees are locked in the same order
+** by all database connections. The p->pNext is a list of other
+** Btrees belonging to the same database connection as the p Btree
+** which need to be locked after p. If we cannot get a lock on
+** p, then first unlock all of the others on p->pNext, then wait
+** for the lock to become available on p, then relock all of the
+** subsequent Btrees that desire a lock.
+*/
+void sqlite3BtreeEnter(Btree *p){
+ Btree *pLater;
+
+ /* Some basic sanity checking on the Btree. The list of Btrees
+ ** connected by pNext and pPrev should be in sorted order by
+ ** Btree.pBt value. All elements of the list should belong to
+ ** the same connection. Only shared Btrees are on the list. */
+ assert( p->pNext==0 || p->pNext->pBt>p->pBt );
+ assert( p->pPrev==0 || p->pPrev->pBt<p->pBt );
+ assert( p->pNext==0 || p->pNext->pSqlite==p->pSqlite );
+ assert( p->pPrev==0 || p->pPrev->pSqlite==p->pSqlite );
+ assert( p->sharable || (p->pNext==0 && p->pPrev==0) );
+
+ /* Check for locking consistency */
+ assert( !p->locked || p->wantToLock>0 );
+ assert( p->sharable || p->wantToLock==0 );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
+
+ if( !p->sharable ) return;
+ p->wantToLock++;
+ if( p->locked ) return;
+
+ /* In most cases, we should be able to acquire the lock we
+ ** want without having to go throught the ascending lock
+ ** procedure that follows. Just be sure not to block.
+ */
+ if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){
+ p->locked = 1;
+ return;
+ }
+
+ /* To avoid deadlock, first release all locks with a larger
+ ** BtShared address. Then acquire our lock. Then reacquire
+ ** the other BtShared locks that we used to hold in ascending
+ ** order.
+ */
+ for(pLater=p->pNext; pLater; pLater=pLater->pNext){
+ assert( pLater->sharable );
+ assert( pLater->pNext==0 || pLater->pNext->pBt>pLater->pBt );
+ assert( !pLater->locked || pLater->wantToLock>0 );
+ if( pLater->locked ){
+ sqlite3_mutex_leave(pLater->pBt->mutex);
+ pLater->locked = 0;
+ }
+ }
+ sqlite3_mutex_enter(p->pBt->mutex);
+ for(pLater=p->pNext; pLater; pLater=pLater->pNext){
+ if( pLater->wantToLock ){
+ sqlite3_mutex_enter(pLater->pBt->mutex);
+ pLater->locked = 1;
+ }
+ }
+}
+
+/*
+** Exit the recursive mutex on a Btree.
+*/
+void sqlite3BtreeLeave(Btree *p){
+ if( p->sharable ){
+ assert( p->wantToLock>0 );
+ p->wantToLock--;
+ if( p->wantToLock==0 ){
+ assert( p->locked );
+ sqlite3_mutex_leave(p->pBt->mutex);
+ p->locked = 0;
+ }
+ }
+}
+
+/*
+** Potentially dd a new Btree pointer to a BtreeMutexSet.
+** Really only add the Btree if it can possibly be shared with
+** another database connection.
+**
+** The Btrees are kept in sorted order by pBtree->pBt. That
+** way when we go to enter all the mutexes, we can enter them
+** in order without every having to backup and retry and without
+** worrying about deadlock.
+**
+** The number of shared btrees will always be small (usually 0 or 1)
+** so an insertion sort is an adequate algorithm here.
+*/
+void sqlite3BtreeMutexSetInsert(BtreeMutexSet *pSet, Btree *pBtree){
+ int i, j;
+ BtShared *pBt;
+ if( !pBtree->sharable ) return;
+#ifndef NDEBUG
+ {
+ for(i=0; i<pSet->nMutex; i++){
+ assert( pSet->aBtree[i]!=pBtree );
+ }
+ }
+#endif
+ assert( pSet->nMutex>=0 );
+ assert( pSet->nMutex<sizeof(pSet->aBtree)/sizeof(pSet->aBtree[0])-1 );
+ pBt = pBtree->pBt;
+ for(i=0; i<pSet->nMutex; i++){
+ assert( pSet->aBtree[i]!=pBtree );
+ if( pSet->aBtree[i]->pBt>pBt ){
+ for(j=pSet->nMutex; j>i; j--){
+ pSet->aBtree[j] = pSet->aBtree[j-1];
+ }
+ pSet->aBtree[i] = pBtree;
+ return;
+ }
+ }
+ pSet->aBtree[pSet->nMutex++] = pBtree;
+}
+
+/*
+** Enter the mutex of every btree in the set. This routine is
+** called at the beginning of sqlite3VdbeExec(). The mutexes are
+** exited at the end of the same function.
+*/
+void sqlite3BtreeMutexSetEnter(BtreeMutexSet *pSet){
+ int i;
+ for(i=0; i<pSet->nMutex; i++){
+ Btree *p = pSet->aBtree[i];
+ /* Some basic sanity checking */
+ assert( i==0 || pSet->aBtree[i-1]->pBt<p->pBt );
+ assert( !p->locked || p->wantToLock>0 );
+ assert( p->sharable );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
+
+ p->wantToLock++;
+ if( !p->locked ){
+ sqlite3_mutex_enter(p->pBt->mutex);
+ }
+ }
+}
+
+/*
+** Leave the mutex of every btree in the set.
+*/
+void sqlite3BtreeMutexSetLeave(BtreeMutexSet *pSet){
+ int i;
+ for(i=0; i<pSet->nMutex; i++){
+ Btree *p = pSet->aBtree[i];
+ /* Some basic sanity checking */
+ assert( i==0 || pSet->aBtree[i-1]->pBt<p->pBt );
+ assert( p->locked );
+ assert( p->sharable );
+ assert( p->wantToLock>0 );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
+
+ p->wantToLock--;
+ if( p->wantToLock==0 ){
+ sqlite3_mutex_leave(p->pBt->mutex);
+ }
+ }
+}
+
+
+#endif /* SQLITE_THREADSAFE && !SQLITE_OMIT_SHARED_CACHE */
diff --git a/src/btree.c b/src/btree.c
index 3d4e55ac8..cd7b3e1a5 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
-** $Id: btree.c,v 1.412 2007/08/24 11:52:29 danielk1977 Exp $
+** $Id: btree.c,v 1.413 2007/08/28 02:27:52 drh Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
@@ -1388,98 +1388,6 @@ int sqlite3BtreeClose(Btree *p){
#if SQLITE_THREADSAFE && !defined(SQLITE_OMIT_SHARED_CACHE)
/*
-** Enter a mutex on the given BTree object.
-**
-** If the object is not sharable, then no mutex is ever required
-** and this routine is a no-op. The underlying mutex is non-recursive.
-** But we keep a reference count in Btree.wantToLock so the behavior
-** of this interface is recursive.
-**
-** To avoid deadlocks, multiple Btrees are locked in the same order
-** by all database connections. The p->pNext is a list of other
-** Btrees belonging to the same database connection as the p Btree
-** which need to be locked after p. If we cannot get a lock on
-** p, then first unlock all of the others on p->pNext, then wait
-** for the lock to become available on p, then relock all of the
-** subsequent Btrees that desire a lock.
-*/
-void sqlite3BtreeEnter(Btree *p){
- Btree *pLater;
-
- /* Some basic sanity checking on the Btree. The list of Btrees
- ** connected by pNext and pPrev should be in sorted order by
- ** Btree.pBt value. All elements of the list should belong to
- ** the same connection. Only shared Btrees are on the list. */
- assert( p->pNext==0 || p->pNext->pBt>p->pBt );
- assert( p->pPrev==0 || p->pPrev->pBt<p->pBt );
- assert( p->pNext==0 || p->pNext->pSqlite==p->pSqlite );
- assert( p->pPrev==0 || p->pPrev->pSqlite==p->pSqlite );
- assert( p->sharable || (p->pNext==0 && p->pPrev==0) );
-
- /* Check for locking consistency */
- assert( !p->locked || p->wantToLock>0 );
- assert( p->sharable || p->wantToLock==0 );
-
- /* We should already hold a lock on the database connection */
- assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
-
- if( !p->sharable ) return;
- p->wantToLock++;
- if( p->locked ) return;
-
- /* In most cases, we should be able to acquire the lock we
- ** want without having to go throught the ascending lock
- ** procedure that follows. Just be sure not to block.
- */
- if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){
- p->locked = 1;
- return;
- }
-
- /* To avoid deadlock, first release all locks with a larger
- ** BtShared address. Then acquire our lock. Then reacquire
- ** the other BtShared locks that we used to hold in ascending
- ** order.
- */
- for(pLater=p->pNext; pLater; pLater=pLater->pNext){
- assert( pLater->sharable );
- assert( pLater->pNext==0 || pLater->pNext->pBt>pLater->pBt );
- assert( !pLater->locked || pLater->wantToLock>0 );
- if( pLater->locked ){
- sqlite3_mutex_leave(pLater->pBt->mutex);
- pLater->locked = 0;
- }
- }
- sqlite3_mutex_enter(p->pBt->mutex);
- for(pLater=p->pNext; pLater; pLater=pLater->pNext){
- if( pLater->wantToLock ){
- sqlite3_mutex_enter(pLater->pBt->mutex);
- pLater->locked = 1;
- }
- }
-}
-#endif /* !SQLITE_OMIT_SHARED_CACHE */
-
-/*
-** Exit the recursive mutex on a Btree.
-*/
-#if SQLITE_THREADSAFE && !defined(SQLITE_OMIT_SHARED_CACHE)
-void sqlite3BtreeLeave(Btree *p){
- if( p->sharable ){
- assert( p->wantToLock>0 );
- p->wantToLock--;
- if( p->wantToLock==0 ){
- assert( p->locked );
- sqlite3_mutex_leave(p->pBt->mutex);
- p->locked = 0;
- }
- }
-}
-#endif
-
-
-#if SQLITE_THREADSAFE && !defined(SQLITE_OMIT_SHARED_CACHE)
-/*
** Short-cuts for entering and leaving mutexes on a cursor.
*/
static void cursorLeave(BtCursor *p){
@@ -6680,7 +6588,6 @@ char *sqlite3BtreeIntegrityCheck(
*/
const char *sqlite3BtreeGetFilename(Btree *p){
assert( p->pBt->pPager!=0 );
- assert( sqlite3BtreeMutexHeld(p->pBt->mutex) );
return sqlite3PagerFilename(p->pBt->pPager);
}
@@ -6689,7 +6596,6 @@ const char *sqlite3BtreeGetFilename(Btree *p){
*/
const char *sqlite3BtreeGetDirname(Btree *p){
assert( p->pBt->pPager!=0 );
- assert( sqlite3BtreeMutexHeld(p->pBt->mutex) );
return sqlite3PagerDirname(p->pBt->pPager);
}
@@ -6700,7 +6606,6 @@ const char *sqlite3BtreeGetDirname(Btree *p){
*/
const char *sqlite3BtreeGetJournalname(Btree *p){
assert( p->pBt->pPager!=0 );
- assert( sqlite3BtreeMutexHeld(p->pBt->mutex) );
return sqlite3PagerJournalname(p->pBt->pPager);
}
@@ -6780,7 +6685,6 @@ int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
** Return non-zero if a transaction is active.
*/
int sqlite3BtreeIsInTrans(Btree *p){
- assert( sqlite3BtreeMutexHeld(p->pBt->mutex) );
assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
return (p && (p->inTrans==TRANS_WRITE));
}
@@ -6798,7 +6702,6 @@ int sqlite3BtreeIsInStmt(Btree *p){
** Return non-zero if a read (or write) transaction is active.
*/
int sqlite3BtreeIsInReadTrans(Btree *p){
- assert( sqlite3BtreeMutexHeld(p->pBt->mutex) );
assert( sqlite3BtreeMutexHeld(p->pSqlite->mutex) );
return (p && (p->inTrans!=TRANS_NONE));
}
diff --git a/src/btree.h b/src/btree.h
index 60e0c887d..2af90f771 100644
--- a/src/btree.h
+++ b/src/btree.h
@@ -13,7 +13,7 @@
** subsystem. See comments in the source code for a detailed description
** of what each interface routine does.
**
-** @(#) $Id: btree.h,v 1.86 2007/08/22 02:56:43 drh Exp $
+** @(#) $Id: btree.h,v 1.87 2007/08/28 02:27:52 drh Exp $
*/
#ifndef _BTREE_H_
#define _BTREE_H_
@@ -41,6 +41,18 @@
typedef struct Btree Btree;
typedef struct BtCursor BtCursor;
typedef struct BtShared BtShared;
+typedef struct BtreeMutexSet BtreeMutexSet;
+
+/*
+** This structure records all of the Btrees that need to hold
+** a mutex before we enter sqlite3VdbeExec(). The Btrees are
+** are placed in aBtree[] in order of aBtree[]->pBt. That way,
+** we can always lock and unlock them all quickly.
+*/
+struct BtreeMutexSet {
+ int nMutex;
+ Btree *aBtree[SQLITE_MAX_ATTACHED+1];
+};
int sqlite3BtreeOpen(
@@ -170,4 +182,16 @@ void sqlite3BtreeCursorList(Btree*);
int sqlite3BtreePageDump(Btree*, int, int recursive);
#endif
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE
+ void sqlite3BtreeMutexSetEnter(BtreeMutexSet*);
+ void sqlite3BtreeMutexSetLeave(BtreeMutexSet*);
+ void sqlite3BtreeMutexSetInsert(BtreeMutexSet*, Btree*);
+#else
+# define sqlite3BtreeMutexSetEnter(X)
+# define sqlite3BtreeMutexSetLeave(X)
+# define sqlite3BtreeMutexSetInsert(X,Y)
+#endif
+
+
+
#endif /* _BTREE_H_ */
diff --git a/src/build.c b/src/build.c
index a68137ded..a3cb28067 100644
--- a/src/build.c
+++ b/src/build.c
@@ -22,7 +22,7 @@
** COMMIT
** ROLLBACK
**
-** $Id: build.c,v 1.438 2007/08/22 20:18:22 drh Exp $
+** $Id: build.c,v 1.439 2007/08/28 02:27:52 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -164,6 +164,7 @@ void sqlite3FinishCoding(Parse *pParse){
sqlite3VdbeJumpHere(v, pParse->cookieGoto-1);
for(iDb=0, mask=1; iDb<db->nDb; mask<<=1, iDb++){
if( (mask & pParse->cookieMask)==0 ) continue;
+ sqlite3VdbeAddMutexBtree(v, db->aDb[iDb].pBt);
sqlite3VdbeAddOp(v, OP_Transaction, iDb, (mask & pParse->writeMask)!=0);
sqlite3VdbeAddOp(v, OP_VerifyCookie, iDb, pParse->cookieValue[iDb]);
}
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 1ebc23825..bc0e8aab1 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
-** @(#) $Id: sqliteInt.h,v 1.602 2007/08/27 23:26:59 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.603 2007/08/28 02:27:52 drh Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@@ -233,8 +233,8 @@ struct BusyHandler {
** Defer sourcing vdbe.h and btree.h until after the "u8" and
** "BusyHandler typedefs.
*/
-#include "vdbe.h"
#include "btree.h"
+#include "vdbe.h"
#include "pager.h"
diff --git a/src/vdbe.c b/src/vdbe.c
index f23c05c76..4519fc032 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.644 2007/08/21 19:33:57 drh Exp $
+** $Id: vdbe.c,v 1.645 2007/08/28 02:27:52 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -465,6 +465,7 @@ int sqlite3VdbeExec(
if( p->magic!=VDBE_MAGIC_RUN ) return SQLITE_MISUSE;
assert( db->magic==SQLITE_MAGIC_BUSY );
pTos = p->pTos;
+ sqlite3BtreeMutexSetEnter(&p->mtxSet);
if( p->rc==SQLITE_NOMEM ){
/* This happens if a malloc() inside a call to sqlite3_column_text() or
** sqlite3_column_text16() failed. */
@@ -685,10 +686,11 @@ case OP_Halt: { /* no-push */
rc = sqlite3VdbeHalt(p);
assert( rc==SQLITE_BUSY || rc==SQLITE_OK );
if( rc==SQLITE_BUSY ){
- p->rc = SQLITE_BUSY;
- return SQLITE_BUSY;
+ p->rc = rc = SQLITE_BUSY;
+ }else{
+ rc = p->rc ? SQLITE_ERROR : SQLITE_DONE;
}
- return p->rc ? SQLITE_ERROR : SQLITE_DONE;
+ goto vdbe_return;
}
/* Opcode: Integer P1 * *
@@ -1003,7 +1005,8 @@ case OP_Callback: { /* no-push */
p->popStack = pOp->p1;
p->pc = pc + 1;
p->pTos = pTos;
- return SQLITE_ROW;
+ rc = SQLITE_ROW;
+ goto vdbe_return;
}
/* Opcode: Concat P1 P2 *
@@ -2462,15 +2465,16 @@ case OP_AutoCommit: { /* no-push */
p->pTos = pTos;
p->pc = pc;
db->autoCommit = 1-i;
- p->rc = SQLITE_BUSY;
- return SQLITE_BUSY;
+ p->rc = rc = SQLITE_BUSY;
+ goto vdbe_return;
}
}
if( p->rc==SQLITE_OK ){
- return SQLITE_DONE;
+ rc = SQLITE_DONE;
}else{
- return SQLITE_ERROR;
+ rc = SQLITE_ERROR;
}
+ goto vdbe_return;
}else{
sqlite3SetString(&p->zErrMsg,
(!i)?"cannot start a transaction within a transaction":(
@@ -2513,9 +2517,9 @@ case OP_Transaction: { /* no-push */
rc = sqlite3BtreeBeginTrans(pBt, pOp->p2);
if( rc==SQLITE_BUSY ){
p->pc = pc;
- p->rc = SQLITE_BUSY;
+ p->rc = rc = SQLITE_BUSY;
p->pTos = pTos;
- return SQLITE_BUSY;
+ goto vdbe_return;
}
if( rc!=SQLITE_OK && rc!=SQLITE_READONLY /* && rc!=SQLITE_BUSY */ ){
goto abort_due_to_error;
@@ -2755,9 +2759,9 @@ case OP_OpenWrite: { /* no-push */
switch( rc ){
case SQLITE_BUSY: {
p->pc = pc;
- p->rc = SQLITE_BUSY;
+ p->rc = rc = SQLITE_BUSY;
p->pTos = &pTos[1 + (pOp->p2<=0)]; /* Operands must remain on stack */
- return SQLITE_BUSY;
+ goto vdbe_return;
}
case SQLITE_OK: {
int flags = sqlite3BtreeFlags(pCur->pCursor);
@@ -5193,6 +5197,12 @@ vdbe_halt:
}
sqlite3VdbeHalt(p);
p->pTos = pTos;
+
+ /* This is the only way out of this procedure. We have to
+ ** release the mutexes on btrees that were acquired at the
+ ** top. */
+vdbe_return:
+ sqlite3BtreeMutexSetLeave(&p->mtxSet);
return rc;
/* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH
diff --git a/src/vdbe.h b/src/vdbe.h
index f6b145bf1..0f3500a75 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -15,7 +15,7 @@
** or VDBE. The VDBE implements an abstract machine that runs a
** simple program to access and modify the underlying database.
**
-** $Id: vdbe.h,v 1.110 2007/05/08 21:45:28 drh Exp $
+** $Id: vdbe.h,v 1.111 2007/08/28 02:27:52 drh Exp $
*/
#ifndef _SQLITE_VDBE_H_
#define _SQLITE_VDBE_H_
@@ -120,6 +120,7 @@ void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
void sqlite3VdbeJumpHere(Vdbe*, int addr);
void sqlite3VdbeChangeToNoop(Vdbe*, int addr, int N);
void sqlite3VdbeChangeP3(Vdbe*, int addr, const char *zP1, int N);
+void sqlite3VdbeAddMutexBtree(Vdbe*, Btree*);
VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
int sqlite3VdbeMakeLabel(Vdbe*);
void sqlite3VdbeDelete(Vdbe*);
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index cef5cfc24..78e76c29a 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -317,7 +317,7 @@ struct Vdbe {
unsigned uniqueCnt; /* Used by OP_MakeRecord when P2!=0 */
int errorAction; /* Recovery action to do in case of an error */
int inTempTrans; /* True if temp database is transactioned */
- int returnStack[100]; /* Return address stack for OP_Gosub & OP_Return */
+ int returnStack[25]; /* Return address stack for OP_Gosub & OP_Return */
int returnDepth; /* Next unused element in returnStack[] */
int nResColumn; /* Number of columns in one row of the result set */
char **azResColumn; /* Values for one row of result */
@@ -332,6 +332,7 @@ struct Vdbe {
u8 inVtabMethod; /* See comments above */
int nChange; /* Number of db changes made since last reset */
i64 startTime; /* Time when query started - used for profiling */
+ BtreeMutexSet mtxSet; /* Set of Btree mutexes */
int nSql; /* Number of bytes in zSql */
char *zSql; /* Text of the SQL statement that generated this */
#ifdef SQLITE_DEBUG
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 8bf6b831a..80e14b17e 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -658,6 +658,13 @@ static char *displayP3(Op *pOp, char *zTemp, int nTemp){
}
#endif
+/*
+** Add a btree to the set of btrees that might need a mutex.
+*/
+void sqlite3VdbeAddMutexBtree(Vdbe *p, Btree *pBtree){
+ sqlite3BtreeMutexSetInsert(&p->mtxSet, pBtree);
+}
+
#if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
/*