|
|
@@ -25,3 +25,303 @@ Index: src/vdbeaux.c
|
|
|
int sqlite3_vdbe_addop_trace = 0;
|
|
|
#endif
|
|
|
|
|
|
+vendor solution for "Ticket 1159: SQLite forgets to call the busy handler"
|
|
|
+http://www.sqlite.org/cvstrac/tktview?tn=1159
|
|
|
+http://www.mail-archive.com/sqlite-users%40sqlite.org/msg06900.html
|
|
|
+http://www.sqlite.org/cvstrac/chngview?cn=2385
|
|
|
+Index: src/btree.c
|
|
|
+===================================================================
|
|
|
+RCS file: /sqlite/sqlite/src/btree.c,v
|
|
|
+retrieving revision 1.251
|
|
|
+retrieving revision 1.252
|
|
|
+diff -u -d -u -d -r1.251 -r1.252
|
|
|
+--- src/btree.c 10 Mar 2005 17:06:34 -0000 1.251
|
|
|
++++ src/btree.c 14 Mar 2005 02:01:50 -0000 1.252
|
|
|
+@@ -9,7 +9,7 @@
|
|
|
+ ** May you share freely, never taking more than you give.
|
|
|
+ **
|
|
|
+ *************************************************************************
|
|
|
+-** $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
++** $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
+ **
|
|
|
+ ** This file implements a external (disk-based) database using BTrees.
|
|
|
+ ** For a detailed discussion of BTrees, refer to
|
|
|
+@@ -314,6 +314,7 @@
|
|
|
+ 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;
|
|
|
+
|
|
|
+@@ -1291,6 +1292,7 @@
|
|
|
+ ** Change the busy handler callback function.
|
|
|
+ */
|
|
|
+ int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){
|
|
|
++ pBt->pBusyHandler = pHandler;
|
|
|
+ sqlite3pager_set_busyhandler(pBt->pPager, pHandler);
|
|
|
+ return SQLITE_OK;
|
|
|
+ }
|
|
|
+@@ -1480,6 +1482,20 @@
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
++** 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
|
|
|
+@@ -1543,7 +1559,7 @@
|
|
|
+ ** 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
|
|
|
+@@ -1558,43 +1574,60 @@
|
|
|
+ ** 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);
|
|
|
++ 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 ){
|
|
|
+- rc = newDatabase(pBt);
|
|
|
++ pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
|
|
|
++ if( wrflag ) pBt->inStmt = 0;
|
|
|
++ }else{
|
|
|
++ unlockBtreeIfUnused(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;
|
|
|
+ }
|
|
|
+
|
|
|
+@@ -2116,7 +2149,7 @@
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if( pBt->pPage1==0 ){
|
|
|
+- rc = lockBtree(pBt);
|
|
|
++ rc = lockBtreeWithRetry(pBt);
|
|
|
+ if( rc!=SQLITE_OK ){
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+@@ -5531,7 +5564,7 @@
|
|
|
+ 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;
|
|
|
+Index: src/pager.c
|
|
|
+===================================================================
|
|
|
+RCS file: /sqlite/sqlite/src/pager.c,v
|
|
|
+retrieving revision 1.192
|
|
|
+retrieving revision 1.193
|
|
|
+diff -u -d -u -d -r1.192 -r1.193
|
|
|
+--- src/pager.c 10 Mar 2005 14:11:13 -0000 1.192
|
|
|
++++ src/pager.c 14 Mar 2005 02:01:50 -0000 1.193
|
|
|
+@@ -18,7 +18,7 @@
|
|
|
+ ** file simultaneously, or one process from reading the database while
|
|
|
+ ** another is writing.
|
|
|
+ **
|
|
|
+-** @(#) $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
++** @(#) $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
+ */
|
|
|
+ #include "sqliteInt.h"
|
|
|
+ #include "os.h"
|
|
|
+@@ -1824,12 +1824,12 @@
|
|
|
+ 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;
|
|
|
+@@ -2633,11 +2633,7 @@
|
|
|
+ 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 ){
|
|
|
+Index: test/lock.test
|
|
|
+===================================================================
|
|
|
+RCS file: /sqlite/sqlite/test/lock.test,v
|
|
|
+retrieving revision 1.30
|
|
|
+retrieving revision 1.31
|
|
|
+diff -u -d -u -d -r1.30 -r1.31
|
|
|
+--- test/lock.test 12 Jan 2005 12:44:04 -0000 1.30
|
|
|
++++ test/lock.test 14 Mar 2005 02:01:50 -0000 1.31
|
|
|
+@@ -11,7 +11,7 @@
|
|
|
+ # This file implements regression tests for SQLite library. The
|
|
|
+ # focus of this script is database locks.
|
|
|
+ #
|
|
|
+-# $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
++# $Id: sqlite.patch.v3,v 1.2 2005/03/14 09:16:06 thl Exp $
|
|
|
+
|
|
|
+
|
|
|
+ set testdir [file dirname $argv0]
|
|
|
+@@ -169,30 +169,56 @@
|
|
|
+ } {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
|
|
|
+@@ -255,7 +281,7 @@
|
|
|
+ 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
|
|
|
+
|