diff options
Diffstat (limited to 'src/test_superlock.c')
-rw-r--r-- | src/test_superlock.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/test_superlock.c b/src/test_superlock.c new file mode 100644 index 0000000..45d0d62 --- /dev/null +++ b/src/test_superlock.c @@ -0,0 +1,363 @@ +/* +** 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; + +/* +** An instance of the following structure is allocated for each active +** superlock. The opaque handle returned by sqlite3demo_superlock() is +** actually a pointer to an instance of this structure. +*/ +struct Superlock { + sqlite3 *db; /* Database handle used to lock db */ + int bWal; /* True if db is a WAL database */ +}; +typedef struct Superlock Superlock; + +/* +** 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(Superlock *pLock){ + int rc; /* Return Code */ + sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */ + + rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + + pLock->bWal = 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 ){ + pLock->bWal = 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 */ + + /* 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; +} + +/* +** 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){ + Superlock *p = (Superlock *)pLock; + if( p->bWal ){ + int rc; /* Return code */ + int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; + sqlite3_file *fd = 0; + rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd); + if( rc==SQLITE_OK ){ + fd->pMethods->xShmLock(fd, 2, 1, flags); + fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags); + } + } + sqlite3_close(p->db); + sqlite3_free(p); +} + +/* +** 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() */ +){ + SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */ + int rc; /* Return code */ + Superlock *pLock; + + pLock = sqlite3_malloc(sizeof(Superlock)); + if( !pLock ) return SQLITE_NOMEM; + memset(pLock, 0, sizeof(Superlock)); + + /* Open a database handle on the file to superlock. */ + rc = sqlite3_open_v2( + zPath, &pLock->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(pLock->db, superlockBusyHandler, (void *)&busy); + rc = sqlite3_exec(pLock->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 ){ + if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){ + rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = superlockWalLock(pLock->db, &busy); + } + } + } + + if( rc!=SQLITE_OK ){ + sqlite3demo_superunlock(pLock); + *ppLock = 0; + }else{ + *ppLock = pLock; + } + + return rc; +} + +/* +** End of example code. Everything below here is the test harness. +************************************************************************** +************************************************************************** +*************************************************************************/ + + +#ifdef SQLITE_TEST + +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +# ifndef SQLITE_TCLAPI +# define SQLITE_TCLAPI +# endif +#endif + +struct InterpAndScript { + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; +typedef struct InterpAndScript InterpAndScript; + +static void SQLITE_TCLAPI superunlock_del(ClientData cd){ + sqlite3demo_superunlock((void *)cd); +} + +static int SQLITE_TCLAPI 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 SQLITE_TCLAPI 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 ){ + extern const char *sqlite3ErrStr(int); + 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 |