]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add file test_superlock.c with example code for obtaining an exclusive lock on either... superlock
authordan <dan@noemail.net>
Fri, 19 Nov 2010 18:20:09 +0000 (18:20 +0000)
committerdan <dan@noemail.net>
Fri, 19 Nov 2010 18:20:09 +0000 (18:20 +0000)
FossilOrigin-Name: 1a3e7417a2184188fe21c3284e58720da9ca11cf

main.mk
manifest
manifest.uuid
src/tclsqlite.c
src/test_superlock.c [new file with mode: 0644]
test/superlock.test [new file with mode: 0644]

diff --git a/main.mk b/main.mk
index 032e67e423c256e2bd37034ed96b28cec588d14b..974bbe4740cdaa0d235452a962a8bb193003d19b 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -251,6 +251,7 @@ TESTSRC = \
   $(TOP)/src/test_schema.c \
   $(TOP)/src/test_server.c \
   $(TOP)/src/test_stat.c \
+  $(TOP)/src/test_superlock.c \
   $(TOP)/src/test_tclvar.c \
   $(TOP)/src/test_thread.c \
   $(TOP)/src/test_vfs.c \
index 7192b6525d67855474f2b008dc630a8084f84e26..be612c0ef8013e851b67532a8c255b858e7f2dbd 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,8 +1,5 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-C Add\sthe\sSQLITE_FCNTL_FILE_POINTER\sverb\sto\ssqlite3_file_control().
-D 2010-11-19T14:37:49
+C Add\sfile\stest_superlock.c\swith\sexample\scode\sfor\sobtaining\san\sexclusive\slock\son\seither\srollback\sor\swal\smode\sdatabases.
+D 2010-11-19T18:20:10
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in e7a59672eaeb04408d1fa8501618d7501a3c5e39
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -102,7 +99,7 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
 F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
-F main.mk 497c8cb6ae132c88fa184e5e454b0e6336da5693
+F main.mk 05d0f3475dd331896bd607cfb45c5e21b94589ad
 F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
 F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
 F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@@ -185,7 +182,7 @@ F src/sqliteInt.h dd28f6138c74cf4833e032a989b6ff7885798cf6
 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
 F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
-F src/tclsqlite.c e1c485fa323e3ef02e5b10fe6a016e7638013eb9
+F src/tclsqlite.c 77c5c4b8ac7b2d94ee480e1ad626fbd921d948e4
 F src/test1.c c2aa29d0fd6db7506fb7f0de7bff1386078296df
 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
 F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
@@ -220,6 +217,7 @@ F src/test_rtree.c 30c981837445a4e187ee850a49c4760d9642f7c3
 F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0
 F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6
 F src/test_stat.c f682704b5d1ba8e1d4e7e882a6d7922e2dcf066c
+F src/test_superlock.c 714bb877e599f96a4923b5429ab36737c3166b09
 F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa
 F src/test_thread.c bedd05cad673dba53326f3aa468cc803038896c0
 F src/test_vfs.c e10fcca756cafa89438311b31522ac1f95bf784b
@@ -653,6 +651,7 @@ F test/stmt.test 25d64e3dbf9a3ce89558667d7f39d966fe2a71b9
 F test/subquery.test b524f57c9574b2c0347045b4510ef795d4686796
 F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4
 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
+F test/superlock.test f9241e8738fe8d05655fd096e4c8a88ee22d8990
 F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
 F test/table.test 04ba066432430657712d167ebf28080fe878d305
 F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
@@ -890,18 +889,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 0a95589f2166f9ce420e647b73e8c797fe8f4833
-R 3b23e03ba29934a15d55ffe87a7aea6b
-T *bgcolor * #90a0f0
-T *branch * superlock
-T *sym-superlock *
-T -sym-trunk *
-U drh
-Z db6b185bc4809187648ac59523a661c3
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.6 (GNU/Linux)
-
-iD8DBQFM5ovBoxKgR168RlERAhiCAKCL2cgcTOqyxxXfn0ztSMKBMJE8QgCgjFOV
-3YKpNtVtoR78SnCwnp0cH5E=
-=hUYw
------END PGP SIGNATURE-----
+P 4425b0645d0afebe3172201012d501c6992daa38
+R 97173199d99068c3ab4ca954bf3aab8d
+U dan
+Z d5525c4f8f3bbdee961ec9309f0a6abe
index 7bfd9d693bc0181e77429bd0e7dfa5e3450ee924..cfc2ddc02cea53bb19bc23314cbabffe042ebf30 100644 (file)
@@ -1 +1 @@
-4425b0645d0afebe3172201012d501c6992daa38
\ No newline at end of file
+1a3e7417a2184188fe21c3284e58720da9ca11cf
\ No newline at end of file
index 13796bed2fb6b0aa49a7a7618e43311219a4395a..9a6307d8e42c1132c11aed15d56dbc024a85d19c 100644 (file)
@@ -3580,6 +3580,7 @@ static void init_all(Tcl_Interp *interp){
     extern int Sqlitetestrtree_Init(Tcl_Interp*);
     extern int Sqlitequota_Init(Tcl_Interp*);
     extern int Sqlitemultiplex_Init(Tcl_Interp*);
+    extern int SqliteSuperlock_Init(Tcl_Interp*);
 
     Sqliteconfig_Init(interp);
     Sqlitetest1_Init(interp);
@@ -3611,6 +3612,7 @@ static void init_all(Tcl_Interp *interp){
     Sqlitetestrtree_Init(interp);
     Sqlitequota_Init(interp);
     Sqlitemultiplex_Init(interp);
+    SqliteSuperlock_Init(interp);
 
     Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
 
diff --git a/src/test_superlock.c b/src/test_superlock.c
new file mode 100644 (file)
index 0000000..c3e3832
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+** 2010 November 19
+**
+** 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.
+**
+*************************************************************************
+** Example code for obtaining an exclusive lock on an SQLite database
+** file. This method is complicated, but works for both WAL and rollback
+** mode database files. The interface to the example code in this file 
+** consists of the following two functions:
+**
+**   sqlite3demo_superlock()
+**   sqlite3demo_superunlock()
+*/
+
+#include <sqlite3.h>
+#include <string.h>               /* memset(), strlen() */
+#include <assert.h>               /* assert() */
+
+/*
+** A structure to collect a busy-handler callback and argument and a count
+** of the number of times it has been invoked.
+*/
+struct SuperlockBusy {
+  int (*xBusy)(void*,int);        /* Pointer to busy-handler function */
+  void *pBusyArg;                 /* First arg to pass to xBusy */
+  int nBusy;                      /* Number of times xBusy has been invoked */
+};
+typedef struct SuperlockBusy SuperlockBusy;
+
+/*
+** The pCtx pointer passed to this function is actually a pointer to a
+** SuperlockBusy structure. Invoke the busy-handler function encapsulated
+** by the structure and return the result.
+*/
+static int superlockBusyHandler(void *pCtx, int UNUSED){
+  SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;
+  if( pBusy->xBusy==0 ) return 0;
+  return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);
+}
+
+/*
+** This function is used to determine if the main database file for 
+** connection db is open in WAL mode or not. If no error occurs and the
+** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.
+** If it is not in WAL mode, set *pbWal to false.
+**
+** If an error occurs, return an SQLite error code. The value of *pbWal
+** is undefined in this case.
+*/
+static int superlockIsWal(sqlite3 *db, int *pbWal){
+  int rc;                         /* Return Code */
+  sqlite3_stmt *pStmt;            /* Compiled PRAGMA journal_mode statement */
+
+  rc = sqlite3_prepare(db, "PRAGMA main.journal_mode", -1, &pStmt, 0);
+  if( rc!=SQLITE_OK ) return rc;
+
+  *pbWal = 0;
+  if( SQLITE_ROW==sqlite3_step(pStmt) ){
+    const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);
+    if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){
+      *pbWal = 1;
+    }
+  }
+
+  return sqlite3_finalize(pStmt);
+}
+
+/*
+** Obtain an exclusive shm-lock on nByte bytes starting at offset idx
+** of the file fd. If the lock cannot be obtained immediately, invoke
+** the busy-handler until either it is obtained or the busy-handler
+** callback returns 0.
+*/
+static int superlockShmLock(
+  sqlite3_file *fd,               /* Database file handle */
+  int idx,                        /* Offset of shm-lock to obtain */
+  int nByte,                      /* Number of consective bytes to lock */
+  SuperlockBusy *pBusy            /* Busy-handler wrapper object */
+){
+  int rc;
+  int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;
+  do {
+    rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);
+  }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );
+  return rc;
+}
+
+/*
+** Obtain the extra locks on the database file required for WAL databases.
+** Invoke the supplied busy-handler as required.
+*/
+static int superlockWalLock(
+  sqlite3 *db,                    /* Database handle open on WAL database */
+  SuperlockBusy *pBusy            /* Busy handler wrapper object */
+){
+  int rc;                         /* Return code */
+  sqlite3_file *fd = 0;           /* Main database file handle */
+  void volatile *p = 0;           /* Pointer to first page of shared memory */
+  int nBusy = 0;                  /* Number of calls already made to xBusy */
+
+  /* Obtain a pointer to the sqlite3_file object open on the main db file. */
+  rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
+  if( rc!=SQLITE_OK ) return rc;
+
+  /* Obtain the "recovery" lock. Normally, this lock is only obtained by
+  ** clients running database recovery.  
+  */
+  rc = superlockShmLock(fd, 2, 1, pBusy);
+  if( rc!=SQLITE_OK ) return rc;
+
+  /* Zero the start of the first shared-memory page. This means that any
+  ** clients that open read or write transactions from this point on will
+  ** have to run recovery before proceeding. Since they need the "recovery"
+  ** lock that this process is holding to do that, no new read or write
+  ** transactions may now be opened. Nor can a checkpoint be run, for the
+  ** same reason.
+  */
+  rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
+  if( rc!=SQLITE_OK ) return rc;
+  memset((void *)p, 0, 32);
+
+  /* Obtain exclusive locks on all the "read-lock" slots. Once these locks
+  ** are held, it is guaranteed that there are no active reader, writer or 
+  ** checkpointer clients.
+  */
+  rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
+  return rc;
+}
+
+/*
+** Obtain a superlock on the database file identified by zPath, using the
+** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
+** returned and output variable *ppLock is populated with an opaque handle
+** that may be used with sqlite3demo_superunlock() to release the lock.
+**
+** If an error occurs, *ppLock is set to 0 and an SQLite error code 
+** (e.g. SQLITE_BUSY) is returned.
+**
+** If a required lock cannot be obtained immediately and the xBusy parameter
+** to this function is not NULL, then xBusy is invoked in the same way
+** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
+** until either the lock can be obtained or the busy-handler function returns
+** 0 (indicating "give up").
+*/
+int sqlite3demo_superlock(
+  const char *zPath,              /* Path to database file to lock */
+  const char *zVfs,               /* VFS to use to access database file */
+  int (*xBusy)(void*,int),        /* Busy handler callback */
+  void *pBusyArg,                 /* Context arg for busy handler */
+  void **ppLock                   /* OUT: Context to pass to superunlock() */
+){
+  sqlite3 *db = 0;                /* Database handle open on zPath */
+  SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
+  int rc;                         /* Return code */
+
+  /* Open a database handle on the file to superlock. */
+  rc = sqlite3_open_v2(
+      zPath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
+  );
+
+  /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
+  ** a WAL database, this is all we need to do.  
+  **
+  ** A wrapper function is used to invoke the busy-handler instead of
+  ** registering the busy-handler function supplied by the user directly
+  ** with SQLite. This is because the same busy-handler function may be
+  ** invoked directly later on when attempting to obtain the extra locks
+  ** required in WAL mode. By using the wrapper, we are able to guarantee
+  ** that the "nBusy" integer parameter passed to the users busy-handler
+  ** represents the total number of busy-handler invocations made within
+  ** this call to sqlite3demo_superlock(), including any made during the
+  ** "BEGIN EXCLUSIVE".
+  */
+  if( rc==SQLITE_OK ){
+    busy.xBusy = xBusy;
+    busy.pBusyArg = pBusyArg;
+    sqlite3_busy_handler(db, superlockBusyHandler, (void *)&busy);
+    rc = sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
+  }
+
+  /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
+  ** database, call superlockWalLock() to obtain the extra locks required
+  ** to prevent readers, writers and/or checkpointers from accessing the
+  ** db while this process is holding the superlock.
+  **
+  ** Before attempting any WAL locks, commit the transaction started above
+  ** to drop the WAL read and write locks currently held. Otherwise, the
+  ** new WAL locks may conflict with the old.
+  */
+  if( rc==SQLITE_OK ){
+    int bWal;                     /* True for a WAL database, false otherwise */
+    if( SQLITE_OK==(rc = superlockIsWal(db, &bWal)) && bWal ){
+      rc = sqlite3_exec(db, "COMMIT", 0, 0, 0);
+      if( rc==SQLITE_OK ){
+        rc = superlockWalLock(db, &busy);
+      }
+    }
+  }
+
+  if( rc!=SQLITE_OK ){
+    sqlite3_close(db);
+    *ppLock = 0;
+  }else{
+    *ppLock = (void *)db;
+  }
+
+  return rc;
+}
+
+/*
+** Release a superlock held on a database file. The argument passed to 
+** this function must have been obtained from a successful call to
+** sqlite3demo_superlock().
+*/
+void sqlite3demo_superunlock(void *pLock){
+  sqlite3_close((sqlite3 *)pLock);
+}
+
+/*
+** End of example code. Everything below here is the test harness.
+**************************************************************************
+**************************************************************************
+*************************************************************************/
+
+
+#ifdef SQLITE_TEST
+
+#include <tcl.h>
+
+struct InterpAndScript {
+  Tcl_Interp *interp;
+  Tcl_Obj *pScript;
+};
+typedef struct InterpAndScript InterpAndScript;
+
+static void superunlock_del(ClientData cd){
+  sqlite3demo_superunlock((void *)cd);
+}
+
+static int superunlock_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+  Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+  return TCL_OK;
+}
+
+static int superlock_busy(void *pCtx, int nBusy){
+  InterpAndScript *p = (InterpAndScript *)pCtx;
+  Tcl_Obj *pEval;                 /* Script to evaluate */
+  int iVal = 0;                   /* Value to return */
+
+  pEval = Tcl_DuplicateObj(p->pScript);
+  Tcl_IncrRefCount(pEval);
+  Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
+  Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
+  Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
+  Tcl_DecrRefCount(pEval);
+
+  return iVal;
+}
+
+/*
+** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
+*/
+static int superlock_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void *pLock;                    /* Lock context */
+  char *zPath;
+  char *zVfs = 0;
+  InterpAndScript busy = {0, 0};
+  int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
+  int rc;                         /* Return code from sqlite3demo_superlock() */
+
+  if( objc<3 || objc>5 ){
+    Tcl_WrongNumArgs(
+        interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
+    return TCL_ERROR;
+  }
+
+  zPath = Tcl_GetString(objv[2]);
+
+  if( objc>3 ){
+    zVfs = Tcl_GetString(objv[3]);
+    if( strlen(zVfs)==0 ) zVfs = 0;
+  }
+  if( objc>4 ){
+    busy.interp = interp;
+    busy.pScript = objv[4];
+    xBusy = superlock_busy;
+  }
+
+  rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
+  assert( rc==SQLITE_OK || pLock==0 );
+  assert( rc!=SQLITE_OK || pLock!=0 );
+
+  if( rc!=SQLITE_OK ){
+    Tcl_ResetResult(interp);
+    Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
+    return TCL_ERROR;
+  }
+
+  Tcl_CreateObjCommand(
+      interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
+  );
+  Tcl_SetObjResult(interp, objv[1]);
+  return TCL_OK;
+}
+
+int SqliteSuperlock_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
+  return TCL_OK;
+}
+#endif
diff --git a/test/superlock.test b/test/superlock.test
new file mode 100644 (file)
index 0000000..4a1f9ed
--- /dev/null
@@ -0,0 +1,98 @@
+# 2010 November 19
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+
+set testprefix superlock
+
+do_execsql_test 1.1 {
+  CREATE TABLE t1(a, b);
+  INSERT INTO t1 VALUES(1, 2);
+  PRAGMA journal_mode = DELETE;
+} {delete}
+
+do_test 1.2 { sqlite3demo_superlock unlock test.db } {unlock}
+do_catchsql_test 1.3 { SELECT * FROM t1 } {1 {database is locked}}
+do_test 1.4 { unlock } {}
+
+do_execsql_test 2.1 { 
+  INSERT INTO t1 VALUES(3, 4);
+  PRAGMA journal_mode = WAL;
+} {wal}
+
+do_test 2.2 { sqlite3demo_superlock unlock test.db } {unlock}
+do_catchsql_test 2.3 { SELECT * FROM t1 }           {1 {database is locked}}
+do_catchsql_test 2.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
+do_catchsql_test 2.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
+do_test 2.6 { unlock } {}
+
+do_execsql_test 3.1 { INSERT INTO t1 VALUES(3, 4) } 
+
+do_test 3.2 { sqlite3demo_superlock unlock test.db } {unlock}
+do_catchsql_test 3.3 { SELECT * FROM t1 }           {1 {database is locked}}
+do_catchsql_test 3.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
+do_catchsql_test 3.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
+do_test 3.6 { unlock } {}
+
+do_execsql_test 4.1 { PRAGMA wal_checkpoint } {}
+
+do_test 4.2 { sqlite3demo_superlock unlock test.db } {unlock}
+do_catchsql_test 4.3 { SELECT * FROM t1 }           {1 {database is locked}}
+do_catchsql_test 4.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
+do_catchsql_test 4.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
+do_test 4.6 { unlock } {}
+
+do_multiclient_test tn {
+  proc busyhandler {x} {
+    switch -- $x {
+      1 { sql1 "COMMIT" }
+      2 { sql2 "COMMIT" }
+      3 { sql3 "COMMIT" }
+    }
+    lappend ::busylist $x
+    return 1
+  }
+  set ::busylist [list]
+
+  do_test 5.$tn.1 {
+    sql1 {
+      CREATE TABLE t1(a, b);
+      PRAGMA journal_mode = WAL;
+      INSERT INTO t1 VALUES(1, 2);
+    }
+  } {wal}
+
+  do_test 5.$tn.2 {
+    sql1 { BEGIN ; SELECT * FROM t1 }
+    sql2 { BEGIN ; INSERT INTO t1 VALUES(3, 4) }
+    sql3 { BEGIN ; SELECT * FROM t1 }
+  } {1 2}
+
+  do_test 5.$tn.3 {
+    set ::busylist [list]
+    sqlite3demo_superlock unlock test.db "" busyhandler
+    set ::busylist
+  } {0 1 2 3}
+
+  do_test 5.$tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
+  do_test 5.$tn.5 { 
+    csql3 { INSERT INTO t1 VALUES(5, 6) } 
+  } {1 {database is locked}}
+  do_test 5.$tn.6 { csql1 "PRAGMA wal_checkpoint" } {1 {database is locked}}
+
+  do_test 5.$tn.7 { unlock } {}
+}
+
+
+finish_test