diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/btmutex.c | 199 | ||||
-rw-r--r-- | src/btree.c | 99 | ||||
-rw-r--r-- | src/btree.h | 26 | ||||
-rw-r--r-- | src/build.c | 3 | ||||
-rw-r--r-- | src/sqliteInt.h | 4 | ||||
-rw-r--r-- | src/vdbe.c | 36 | ||||
-rw-r--r-- | src/vdbe.h | 3 | ||||
-rw-r--r-- | src/vdbeInt.h | 3 | ||||
-rw-r--r-- | src/vdbeaux.c | 7 |
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) /* |