Permalink
Browse files

Add file test_superlock.c with example code for obtaining an exclusiv…

…e lock on either rollback or wal mode databases.
  • Loading branch information...
Unknown committed Nov 19, 2010
1 parent 4124e5d commit 42845b20b2a293e7e7368791fe86782b2d13e896
Showing with 431 additions and 0 deletions.
  1. +1 −0 main.mk
  2. +2 −0 src/tclsqlite.c
  3. +330 −0 src/test_superlock.c
  4. +98 −0 test/superlock.test
View
@@ -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 \
View
@@ -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);
View
@@ -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
Oops, something went wrong.

0 comments on commit 42845b2

Please sign in to comment.