AryaWu/sqlite
0
1/*2** 2010 November 193**4** The author disclaims copyright to this source code. In place of5** a legal notice, here is a blessing:6**7** May you do good and not evil.8** May you find forgiveness for yourself and forgive others.9** May you share freely, never taking more than you give.10**11*************************************************************************12** Example code for obtaining an exclusive lock on an SQLite database13** file. This method is complicated, but works for both WAL and rollback14** mode database files. The interface to the example code in this file 15** consists of the following two functions:16**17** sqlite3demo_superlock()18** sqlite3demo_superunlock()19*/20 21#include "sqlite3.h"22#include <string.h> /* memset(), strlen() */23#include <assert.h> /* assert() */24 25/*26** A structure to collect a busy-handler callback and argument and a count27** of the number of times it has been invoked.28*/29struct SuperlockBusy {30 int (*xBusy)(void*,int); /* Pointer to busy-handler function */31 void *pBusyArg; /* First arg to pass to xBusy */32 int nBusy; /* Number of times xBusy has been invoked */33};34typedef struct SuperlockBusy SuperlockBusy;35 36/*37** An instance of the following structure is allocated for each active38** superlock. The opaque handle returned by sqlite3demo_superlock() is39** actually a pointer to an instance of this structure.40*/41struct Superlock {42 sqlite3 *db; /* Database handle used to lock db */43 int bWal; /* True if db is a WAL database */44};45typedef struct Superlock Superlock;46 47/*48** The pCtx pointer passed to this function is actually a pointer to a49** SuperlockBusy structure. Invoke the busy-handler function encapsulated50** by the structure and return the result.51*/52static int superlockBusyHandler(void *pCtx, int UNUSED){53 SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;54 if( pBusy->xBusy==0 ) return 0;55 return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);56}57 58/*59** This function is used to determine if the main database file for 60** connection db is open in WAL mode or not. If no error occurs and the61** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.62** If it is not in WAL mode, set *pbWal to false.63**64** If an error occurs, return an SQLite error code. The value of *pbWal65** is undefined in this case.66*/67static int superlockIsWal(Superlock *pLock){68 int rc; /* Return Code */69 sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */70 71 rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0);72 if( rc!=SQLITE_OK ) return rc;73 74 pLock->bWal = 0;75 if( SQLITE_ROW==sqlite3_step(pStmt) ){76 const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);77 if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){78 pLock->bWal = 1;79 }80 }81 82 return sqlite3_finalize(pStmt);83}84 85/*86** Obtain an exclusive shm-lock on nByte bytes starting at offset idx87** of the file fd. If the lock cannot be obtained immediately, invoke88** the busy-handler until either it is obtained or the busy-handler89** callback returns 0.90*/91static int superlockShmLock(92 sqlite3_file *fd, /* Database file handle */93 int idx, /* Offset of shm-lock to obtain */94 int nByte, /* Number of consective bytes to lock */95 SuperlockBusy *pBusy /* Busy-handler wrapper object */96){97 int rc;98 int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;99 do {100 rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);101 }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );102 return rc;103}104 105/*106** Obtain the extra locks on the database file required for WAL databases.107** Invoke the supplied busy-handler as required.108*/109static int superlockWalLock(110 sqlite3 *db, /* Database handle open on WAL database */111 SuperlockBusy *pBusy /* Busy handler wrapper object */112){113 int rc; /* Return code */114 sqlite3_file *fd = 0; /* Main database file handle */115 void volatile *p = 0; /* Pointer to first page of shared memory */116 117 /* Obtain a pointer to the sqlite3_file object open on the main db file. */118 rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);119 if( rc!=SQLITE_OK ) return rc;120 121 /* Obtain the "recovery" lock. Normally, this lock is only obtained by122 ** clients running database recovery. 123 */124 rc = superlockShmLock(fd, 2, 1, pBusy);125 if( rc!=SQLITE_OK ) return rc;126 127 /* Zero the start of the first shared-memory page. This means that any128 ** clients that open read or write transactions from this point on will129 ** have to run recovery before proceeding. Since they need the "recovery"130 ** lock that this process is holding to do that, no new read or write131 ** transactions may now be opened. Nor can a checkpoint be run, for the132 ** same reason.133 */134 rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);135 if( rc!=SQLITE_OK ) return rc;136 memset((void *)p, 0, 32);137 138 /* Obtain exclusive locks on all the "read-lock" slots. Once these locks139 ** are held, it is guaranteed that there are no active reader, writer or 140 ** checkpointer clients.141 */142 rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);143 return rc;144}145 146/*147** Release a superlock held on a database file. The argument passed to 148** this function must have been obtained from a successful call to149** sqlite3demo_superlock().150*/151void sqlite3demo_superunlock(void *pLock){152 Superlock *p = (Superlock *)pLock;153 if( p->bWal ){154 int rc; /* Return code */155 int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;156 sqlite3_file *fd = 0;157 rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);158 if( rc==SQLITE_OK ){159 fd->pMethods->xShmLock(fd, 2, 1, flags);160 fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags);161 }162 }163 sqlite3_close(p->db);164 sqlite3_free(p);165}166 167/*168** Obtain a superlock on the database file identified by zPath, using the169** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is170** returned and output variable *ppLock is populated with an opaque handle171** that may be used with sqlite3demo_superunlock() to release the lock.172**173** If an error occurs, *ppLock is set to 0 and an SQLite error code 174** (e.g. SQLITE_BUSY) is returned.175**176** If a required lock cannot be obtained immediately and the xBusy parameter177** to this function is not NULL, then xBusy is invoked in the same way178** as a busy-handler registered with SQLite (using sqlite3_busy_handler())179** until either the lock can be obtained or the busy-handler function returns180** 0 (indicating "give up").181*/182int sqlite3demo_superlock(183 const char *zPath, /* Path to database file to lock */184 const char *zVfs, /* VFS to use to access database file */185 int (*xBusy)(void*,int), /* Busy handler callback */186 void *pBusyArg, /* Context arg for busy handler */187 void **ppLock /* OUT: Context to pass to superunlock() */188){189 SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */190 int rc; /* Return code */191 Superlock *pLock;192 193 pLock = sqlite3_malloc(sizeof(Superlock));194 if( !pLock ) return SQLITE_NOMEM;195 memset(pLock, 0, sizeof(Superlock));196 197 /* Open a database handle on the file to superlock. */198 rc = sqlite3_open_v2(199 zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs200 );201 202 /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not203 ** a WAL database, this is all we need to do. 204 **205 ** A wrapper function is used to invoke the busy-handler instead of206 ** registering the busy-handler function supplied by the user directly207 ** with SQLite. This is because the same busy-handler function may be208 ** invoked directly later on when attempting to obtain the extra locks209 ** required in WAL mode. By using the wrapper, we are able to guarantee210 ** that the "nBusy" integer parameter passed to the users busy-handler211 ** represents the total number of busy-handler invocations made within212 ** this call to sqlite3demo_superlock(), including any made during the213 ** "BEGIN EXCLUSIVE".214 */215 if( rc==SQLITE_OK ){216 busy.xBusy = xBusy;217 busy.pBusyArg = pBusyArg;218 sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy);219 rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0);220 }221 222 /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL223 ** database, call superlockWalLock() to obtain the extra locks required224 ** to prevent readers, writers and/or checkpointers from accessing the225 ** db while this process is holding the superlock.226 **227 ** Before attempting any WAL locks, commit the transaction started above228 ** to drop the WAL read and write locks currently held. Otherwise, the229 ** new WAL locks may conflict with the old.230 */231 if( rc==SQLITE_OK ){232 if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){233 rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0);234 if( rc==SQLITE_OK ){235 rc = superlockWalLock(pLock->db, &busy);236 }237 }238 }239 240 if( rc!=SQLITE_OK ){241 sqlite3demo_superunlock(pLock);242 *ppLock = 0;243 }else{244 *ppLock = pLock;245 }246 247 return rc;248}249 250/*251** End of example code. Everything below here is the test harness.252**************************************************************************253**************************************************************************254*************************************************************************/255 256 257#ifdef SQLITE_TEST258 259#include "tclsqlite.h"260 261struct InterpAndScript {262 Tcl_Interp *interp;263 Tcl_Obj *pScript;264};265typedef struct InterpAndScript InterpAndScript;266 267static void SQLITE_TCLAPI superunlock_del(ClientData cd){268 sqlite3demo_superunlock((void *)cd);269}270 271static int SQLITE_TCLAPI superunlock_cmd(272 ClientData cd,273 Tcl_Interp *interp,274 int objc,275 Tcl_Obj *CONST objv[]276){277 if( objc!=1 ){278 Tcl_WrongNumArgs(interp, 1, objv, "");279 return TCL_ERROR;280 }281 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));282 return TCL_OK;283}284 285static int superlock_busy(void *pCtx, int nBusy){286 InterpAndScript *p = (InterpAndScript *)pCtx;287 Tcl_Obj *pEval; /* Script to evaluate */288 int iVal = 0; /* Value to return */289 290 pEval = Tcl_DuplicateObj(p->pScript);291 Tcl_IncrRefCount(pEval);292 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));293 Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);294 Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);295 Tcl_DecrRefCount(pEval);296 297 return iVal;298}299 300/*301** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT302*/303static int SQLITE_TCLAPI superlock_cmd(304 ClientData cd,305 Tcl_Interp *interp,306 int objc,307 Tcl_Obj *CONST objv[]308){309 void *pLock; /* Lock context */310 char *zPath;311 char *zVfs = 0;312 InterpAndScript busy = {0, 0};313 int (*xBusy)(void*,int) = 0; /* Busy handler callback */314 int rc; /* Return code from sqlite3demo_superlock() */315 316 if( objc<3 || objc>5 ){317 Tcl_WrongNumArgs(318 interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");319 return TCL_ERROR;320 }321 322 zPath = Tcl_GetString(objv[2]);323 324 if( objc>3 ){325 zVfs = Tcl_GetString(objv[3]);326 if( strlen(zVfs)==0 ) zVfs = 0;327 }328 if( objc>4 ){329 busy.interp = interp;330 busy.pScript = objv[4];331 xBusy = superlock_busy;332 }333 334 rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);335 assert( rc==SQLITE_OK || pLock==0 );336 assert( rc!=SQLITE_OK || pLock!=0 );337 338 if( rc!=SQLITE_OK ){339 extern const char *sqlite3ErrStr(int);340 Tcl_ResetResult(interp);341 Tcl_AppendResult(interp, sqlite3ErrStr(rc), NULL);342 return TCL_ERROR;343 }344 345 Tcl_CreateObjCommand(346 interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del347 );348 Tcl_SetObjResult(interp, objv[1]);349 return TCL_OK;350}351 352int SqliteSuperlock_Init(Tcl_Interp *interp){353 Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);354 return TCL_OK;355}356#endif357 