Browse Source

greatly enhance parallel writer capability of SQLite3 by importing vendor solution for "SQLite forgets to call the busy handler"

Thomas Lotterer 20 years ago
parent
commit
d8406979b1
2 changed files with 301 additions and 1 deletions
  1. 300 0
      sqlite/sqlite.patch.v3
  2. 1 1
      sqlite/sqlite.spec

+ 300 - 0
sqlite/sqlite.patch.v3

@@ -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
+

+ 1 - 1
sqlite/sqlite.spec

@@ -38,7 +38,7 @@ Class:        BASE
 Group:        Database
 License:      PD
 Version:      %{V_v2}
-Release:      20050312
+Release:      20050314
 
 #   package options
 %option       with_utf8            no