Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -7,11 +7,11 @@ ** 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: btree.c,v 1.251 2005/03/10 17:06:34 drh Exp $ +** $Id: btree.c,v 1.252 2005/03/14 02:01:50 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: @@ -312,10 +312,11 @@ u16 usableSize; /* Number of usable bytes on each page */ int maxLocal; /* Maximum local payload in non-LEAFDATA tables */ int minLocal; /* Minimum local payload in non-LEAFDATA tables */ int maxLeaf; /* Maximum local payload in a LEAFDATA table */ int minLeaf; /* Minimum local payload in a LEAFDATA table */ + BusyHandler *pBusyHandler; /* Callback for when there is lock contention */ }; typedef Btree Bt; /* ** Btree.inTrans may take one of the following values. @@ -1289,10 +1290,11 @@ /* ** Change the busy handler callback function. */ int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){ + pBt->pBusyHandler = pHandler; sqlite3pager_set_busyhandler(pBt->pPager, pHandler); return SQLITE_OK; } /* @@ -1477,10 +1479,24 @@ releasePage(pPage1); pBt->pPage1 = 0; return rc; } +/* +** This routine works like lockBtree() except that it also invokes the +** busy callback if there is lock contention. +*/ +static int lockBtreeWithRetry(Btree *pBt){ + int rc = SQLITE_OK; + if( pBt->inTrans==TRANS_NONE ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + pBt->inTrans = TRANS_NONE; + } + return rc; +} + + /* ** If there are no outstanding cursors and we are not in the middle ** of a transaction but there is a read lock on the database, then ** this routine unrefs the first page of the database file which ** has the effect of releasing the read lock. @@ -1541,11 +1557,11 @@ ** Attempt to start a new transaction. A write-transaction ** is started if the second argument is nonzero, otherwise a read- ** transaction. If the second argument is 2 or more and exclusive ** transaction is started, meaning that no other process is allowed ** to access the database. A preexisting transaction may not be -** upgrade to exclusive by calling this routine a second time - the +** upgraded to exclusive by calling this routine a second time - the ** exclusivity flag only works for a new transaction. ** ** A write-transaction must be started before attempting any ** changes to the database. None of the following routines ** will work unless a transaction is started first: @@ -1556,47 +1572,64 @@ ** sqlite3BtreeDropTable() ** sqlite3BtreeInsert() ** sqlite3BtreeDelete() ** sqlite3BtreeUpdateMeta() ** -** If wrflag is true, then nMaster specifies the maximum length of -** a master journal file name supplied later via sqlite3BtreeSync(). -** This is so that appropriate space can be allocated in the journal file -** when it is created.. +** If an initial attempt to acquire the lock fails because of lock contention +** and the database was previously unlocked, then invoke the busy handler +** if there is one. But if there was previously a read-lock, do not +** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is +** returned when there is already a read-lock in order to avoid a deadlock. +** +** Suppose there are two processes A and B. A has a read lock and B has +** a reserved lock. B tries to promote to exclusive but is blocked because +** of A's read lock. A tries to promote to reserved but is blocked by B. +** One or the other of the two processes must give way or there can be +** no progress. By returning SQLITE_BUSY and not invoking the busy callback +** when A already has a read lock, we encourage A to give up and let B +** proceed. */ int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){ int rc = SQLITE_OK; + int busy = 0; + BusyHandler *pH; /* If the btree is already in a write-transaction, or it ** is already in a read-transaction and a read-transaction ** is requested, this is a no-op. */ - if( pBt->inTrans==TRANS_WRITE || - (pBt->inTrans==TRANS_READ && !wrflag) ){ + if( pBt->inTrans==TRANS_WRITE || (pBt->inTrans==TRANS_READ && !wrflag) ){ return SQLITE_OK; } + + /* Write transactions are not possible on a read-only database */ if( pBt->readOnly && wrflag ){ return SQLITE_READONLY; } - if( pBt->pPage1==0 ){ - rc = lockBtree(pBt); - } - - if( rc==SQLITE_OK && wrflag ){ - rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1); - if( rc==SQLITE_OK ){ - rc = newDatabase(pBt); - } - } - - if( rc==SQLITE_OK ){ - pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ); - if( wrflag ) pBt->inStmt = 0; - }else{ - unlockBtreeIfUnused(pBt); - } + do { + if( pBt->pPage1==0 ){ + rc = lockBtree(pBt); + } + + if( rc==SQLITE_OK && wrflag ){ + rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1); + if( rc==SQLITE_OK ){ + rc = newDatabase(pBt); + } + } + + if( rc==SQLITE_OK ){ + pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ); + if( wrflag ) pBt->inStmt = 0; + }else{ + unlockBtreeIfUnused(pBt); + } + }while( rc==SQLITE_BUSY && pBt->inTrans==TRANS_NONE && + (pH = pBt->pBusyHandler)!=0 && + pH->xFunc && pH->xFunc(pH->pArg, busy++) + ); return rc; } #ifndef SQLITE_OMIT_AUTOVACUUM @@ -2114,11 +2147,11 @@ if( checkReadLocks(pBt, iTable, 0) ){ return SQLITE_LOCKED; } } if( pBt->pPage1==0 ){ - rc = lockBtree(pBt); + rc = lockBtreeWithRetry(pBt); if( rc!=SQLITE_OK ){ return rc; } } pCur = sqliteMallocRaw( sizeof(*pCur) ); @@ -5529,11 +5562,11 @@ int i; int nRef; IntegrityCk sCheck; nRef = *sqlite3pager_stats(pBt->pPager); - if( lockBtree(pBt)!=SQLITE_OK ){ + if( lockBtreeWithRetry(pBt)!=SQLITE_OK ){ return sqliteStrDup("Unable to acquire a read lock on the database"); } sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = sqlite3pager_pagecount(sCheck.pPager); Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -16,11 +16,11 @@ ** is separate from the database file. The pager also implements file ** locking to prevent two processes from writing the same database ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.192 2005/03/10 14:11:13 drh Exp $ +** @(#) $Id: pager.c,v 1.193 2005/03/14 02:01:50 drh Exp $ */ #include "sqliteInt.h" #include "os.h" #include "pager.h" #include @@ -1822,16 +1822,16 @@ assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK ); if( pPager->state>=locktype ){ rc = SQLITE_OK; }else{ int busy = 1; + BusyHandler *pH; do { rc = sqlite3OsLock(&pPager->fd, locktype); }while( rc==SQLITE_BUSY && - pPager->pBusyHandler && - pPager->pBusyHandler->xFunc && - pPager->pBusyHandler->xFunc(pPager->pBusyHandler->pArg, busy++) + (pH = pPager->pBusyHandler)!=0 && + pH->xFunc && pH->xFunc(pH->pArg, busy++) ); if( rc==SQLITE_OK ){ pPager->state = locktype; } } @@ -2631,15 +2631,11 @@ assert( pPager->aInJournal==0 ); if( MEMDB ){ pPager->state = PAGER_EXCLUSIVE; pPager->origDbSize = pPager->dbSize; }else{ - if( SQLITE_BUSY_RESERVED_LOCK || exFlag ){ - rc = pager_wait_on_lock(pPager, RESERVED_LOCK); - }else{ - rc = sqlite3OsLock(&pPager->fd, RESERVED_LOCK); - } + rc = sqlite3OsLock(&pPager->fd, RESERVED_LOCK); if( rc==SQLITE_OK ){ pPager->state = PAGER_RESERVED; if( exFlag ){ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); } Index: test/lock.test ================================================================== --- test/lock.test +++ test/lock.test @@ -9,11 +9,11 @@ # #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this script is database locks. # -# $Id: lock.test,v 1.30 2005/01/12 12:44:04 danielk1977 Exp $ +# $Id: lock.test,v 1.31 2005/03/14 02:01:50 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -167,34 +167,60 @@ do_test lock-2.2 { catchsql {SELECT * FROM t2} db2 } {0 {9 8}} # If the other thread (the one that does not hold the transaction with -# a RESERVED lock) tries to get a RESERVED lock, we do not get a busy callback. +# a RESERVED lock) tries to get a RESERVED lock, we do get a busy callback +# as long as we were not orginally holding a READ lock. # -do_test lock-2.3 { +do_test lock-2.3.1 { proc callback {count} { set ::callback_value $count break } set ::callback_value {} db2 busy callback + # db2 does not hold a lock so we should get a busy callback here + set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg] + lappend r $msg + lappend r $::callback_value +} {1 {database is locked} 0} +do_test lock-2.3.2 { + set ::callback_value {} + execsql {BEGIN; SELECT rowid FROM sqlite_master LIMIT 1} db2 + # This time db2 does hold a read lock. No busy callback this time. set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg] lappend r $msg lappend r $::callback_value } {1 {database is locked} {}} -do_test lock-2.4 { +catch {execsql {ROLLBACK} db2} +do_test lock-2.4.1 { + proc callback {count} { + lappend ::callback_value $count + if {$count>4} break + } + set ::callback_value {} + db2 busy callback + # We get a busy callback because db2 is not holding a lock + set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg] + lappend r $msg + lappend r $::callback_value +} {1 {database is locked} {0 1 2 3 4 5}} +do_test lock-2.4.2 { proc callback {count} { lappend ::callback_value $count if {$count>4} break } set ::callback_value {} db2 busy callback + execsql {BEGIN; SELECT rowid FROM sqlite_master LIMIT 1} db2 + # No busy callback this time because we are holding a lock set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg] lappend r $msg lappend r $::callback_value } {1 {database is locked} {}} +catch {execsql {ROLLBACK} db2} do_test lock-2.5 { proc callback {count} { lappend ::callback_value $count if {$count>4} break } @@ -253,11 +279,11 @@ if {$count>4} break } db2 busy callback set rc [catch {db2 eval {UPDATE t1 SET a=0}} msg] lappend rc $msg $::callback_value -} {1 {database is locked} {}} +} {1 {database is locked} {0 1 2 3 4 5}} execsql {ROLLBACK} # When one thread is writing, other threads cannot read. Except if the # writing thread is writing to its temporary tables, the other threads # can still read. -> Not so in 3.0. One thread can read while another Index: www/capi3ref.tcl ================================================================== --- www/capi3ref.tcl +++ www/capi3ref.tcl @@ -1,6 +1,6 @@ -set rcsid {$Id: capi3ref.tcl,v 1.19 2005/03/12 18:03:59 drh Exp $} +set rcsid {$Id: capi3ref.tcl,v 1.20 2005/03/14 02:01:50 drh Exp $} source common.tcl header {C/C++ Interface For SQLite Version 3} puts {

C/C++ Interface For SQLite Version 3

} @@ -163,11 +163,11 @@ busy callback returns 0, then no additional attempts are made to access the database and SQLITE_BUSY is returned. If the callback returns non-zero, then another attempt is made to open the database for reading and the cycle repeats. - That a busy handler is registered does not guarantee that + The presence of a busy handler does not guarantee that it will be invoked when there is lock contention. If SQLite determines that invoking the busy handler could result in a deadlock, it will return SQLITE_BUSY instead. Consider a scenario where one process is holding a read lock that it is trying to promote to a reserved lock and