Explorar el Código

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

Thomas Lotterer hace 20 años
padre
commit
d8406979b1
Se han modificado 2 ficheros con 301 adiciones y 1 borrados
  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