123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- /*
- ** 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
- #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 ){
- 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
|