1 /* 2 ** 2010 November 19 3 ** 4 ** The author disclaims copyright to this source code. In place of 5 ** 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 database 13 ** file. This method is complicated, but works for both WAL and rollback 14 ** 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 count 27 ** of the number of times it has been invoked. 28 */ 29 struct 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 }; 34 typedef struct SuperlockBusy SuperlockBusy; 35 36 /* 37 ** The pCtx pointer passed to this function is actually a pointer to a 38 ** SuperlockBusy structure. Invoke the busy-handler function encapsulated 39 ** by the structure and return the result. 40 */ 41 static int superlockBusyHandler(void *pCtx, int UNUSED){ 42 SuperlockBusy *pBusy = (SuperlockBusy *)pCtx; 43 if( pBusy->xBusy==0 ) return 0; 44 return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++); 45 } 46 47 /* 48 ** This function is used to determine if the main database file for 49 ** connection db is open in WAL mode or not. If no error occurs and the 50 ** database file is in WAL mode, set *pbWal to true and return SQLITE_OK. 51 ** If it is not in WAL mode, set *pbWal to false. 52 ** 53 ** If an error occurs, return an SQLite error code. The value of *pbWal 54 ** is undefined in this case. 55 */ 56 static int superlockIsWal(sqlite3 *db, int *pbWal){ 57 int rc; /* Return Code */ 58 sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */ 59 60 rc = sqlite3_prepare(db, "PRAGMA main.journal_mode", -1, &pStmt, 0); 61 if( rc!=SQLITE_OK ) return rc; 62 63 *pbWal = 0; 64 if( SQLITE_ROW==sqlite3_step(pStmt) ){ 65 const char *zMode = (const char *)sqlite3_column_text(pStmt, 0); 66 if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){ 67 *pbWal = 1; 68 } 69 } 70 71 return sqlite3_finalize(pStmt); 72 } 73 74 /* 75 ** Obtain an exclusive shm-lock on nByte bytes starting at offset idx 76 ** of the file fd. If the lock cannot be obtained immediately, invoke 77 ** the busy-handler until either it is obtained or the busy-handler 78 ** callback returns 0. 79 */ 80 static int superlockShmLock( 81 sqlite3_file *fd, /* Database file handle */ 82 int idx, /* Offset of shm-lock to obtain */ 83 int nByte, /* Number of consective bytes to lock */ 84 SuperlockBusy *pBusy /* Busy-handler wrapper object */ 85 ){ 86 int rc; 87 int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock; 88 do { 89 rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE); 90 }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) ); 91 return rc; 92 } 93 94 /* 95 ** Obtain the extra locks on the database file required for WAL databases. 96 ** Invoke the supplied busy-handler as required. 97 */ 98 static int superlockWalLock( 99 sqlite3 *db, /* Database handle open on WAL database */ 100 SuperlockBusy *pBusy /* Busy handler wrapper object */ 101 ){ 102 int rc; /* Return code */ 103 sqlite3_file *fd = 0; /* Main database file handle */ 104 void volatile *p = 0; /* Pointer to first page of shared memory */ 105 int nBusy = 0; /* Number of calls already made to xBusy */ 106 107 /* Obtain a pointer to the sqlite3_file object open on the main db file. */ 108 rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd); 109 if( rc!=SQLITE_OK ) return rc; 110 111 /* Obtain the "recovery" lock. Normally, this lock is only obtained by 112 ** clients running database recovery. 113 */ 114 rc = superlockShmLock(fd, 2, 1, pBusy); 115 if( rc!=SQLITE_OK ) return rc; 116 117 /* Zero the start of the first shared-memory page. This means that any 118 ** clients that open read or write transactions from this point on will 119 ** have to run recovery before proceeding. Since they need the "recovery" 120 ** lock that this process is holding to do that, no new read or write 121 ** transactions may now be opened. Nor can a checkpoint be run, for the 122 ** same reason. 123 */ 124 rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p); 125 if( rc!=SQLITE_OK ) return rc; 126 memset((void *)p, 0, 32); 127 128 /* Obtain exclusive locks on all the "read-lock" slots. Once these locks 129 ** are held, it is guaranteed that there are no active reader, writer or 130 ** checkpointer clients. 131 */ 132 rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy); 133 return rc; 134 } 135 136 /* 137 ** Obtain a superlock on the database file identified by zPath, using the 138 ** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is 139 ** returned and output variable *ppLock is populated with an opaque handle 140 ** that may be used with sqlite3demo_superunlock() to release the lock. 141 ** 142 ** If an error occurs, *ppLock is set to 0 and an SQLite error code 143 ** (e.g. SQLITE_BUSY) is returned. 144 ** 145 ** If a required lock cannot be obtained immediately and the xBusy parameter 146 ** to this function is not NULL, then xBusy is invoked in the same way 147 ** as a busy-handler registered with SQLite (using sqlite3_busy_handler()) 148 ** until either the lock can be obtained or the busy-handler function returns 149 ** 0 (indicating "give up"). 150 */ 151 int sqlite3demo_superlock( 152 const char *zPath, /* Path to database file to lock */ 153 const char *zVfs, /* VFS to use to access database file */ 154 int (*xBusy)(void*,int), /* Busy handler callback */ 155 void *pBusyArg, /* Context arg for busy handler */ 156 void **ppLock /* OUT: Context to pass to superunlock() */ 157 ){ 158 sqlite3 *db = 0; /* Database handle open on zPath */ 159 SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */ 160 int rc; /* Return code */ 161 162 /* Open a database handle on the file to superlock. */ 163 rc = sqlite3_open_v2( 164 zPath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs 165 ); 166 167 /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not 168 ** a WAL database, this is all we need to do. 169 ** 170 ** A wrapper function is used to invoke the busy-handler instead of 171 ** registering the busy-handler function supplied by the user directly 172 ** with SQLite. This is because the same busy-handler function may be 173 ** invoked directly later on when attempting to obtain the extra locks 174 ** required in WAL mode. By using the wrapper, we are able to guarantee 175 ** that the "nBusy" integer parameter passed to the users busy-handler 176 ** represents the total number of busy-handler invocations made within 177 ** this call to sqlite3demo_superlock(), including any made during the 178 ** "BEGIN EXCLUSIVE". 179 */ 180 if( rc==SQLITE_OK ){ 181 busy.xBusy = xBusy; 182 busy.pBusyArg = pBusyArg; 183 sqlite3_busy_handler(db, superlockBusyHandler, (void *)&busy); 184 rc = sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0); 185 } 186 187 /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL 188 ** database, call superlockWalLock() to obtain the extra locks required 189 ** to prevent readers, writers and/or checkpointers from accessing the 190 ** db while this process is holding the superlock. 191 ** 192 ** Before attempting any WAL locks, commit the transaction started above 193 ** to drop the WAL read and write locks currently held. Otherwise, the 194 ** new WAL locks may conflict with the old. 195 */ 196 if( rc==SQLITE_OK ){ 197 int bWal; /* True for a WAL database, false otherwise */ 198 if( SQLITE_OK==(rc = superlockIsWal(db, &bWal)) && bWal ){ 199 rc = sqlite3_exec(db, "COMMIT", 0, 0, 0); 200 if( rc==SQLITE_OK ){ 201 rc = superlockWalLock(db, &busy); 202 } 203 } 204 } 205 206 if( rc!=SQLITE_OK ){ 207 sqlite3_close(db); 208 *ppLock = 0; 209 }else{ 210 *ppLock = (void *)db; 211 } 212 213 return rc; 214 } 215 216 /* 217 ** Release a superlock held on a database file. The argument passed to 218 ** this function must have been obtained from a successful call to 219 ** sqlite3demo_superlock(). 220 */ 221 void sqlite3demo_superunlock(void *pLock){ 222 sqlite3_close((sqlite3 *)pLock); 223 } 224 225 /* 226 ** End of example code. Everything below here is the test harness. 227 ************************************************************************** 228 ************************************************************************** 229 *************************************************************************/ 230 231 232 #ifdef SQLITE_TEST 233 234 #include <tcl.h> 235 236 struct InterpAndScript { 237 Tcl_Interp *interp; 238 Tcl_Obj *pScript; 239 }; 240 typedef struct InterpAndScript InterpAndScript; 241 242 static void superunlock_del(ClientData cd){ 243 sqlite3demo_superunlock((void *)cd); 244 } 245 246 static int superunlock_cmd( 247 ClientData cd, 248 Tcl_Interp *interp, 249 int objc, 250 Tcl_Obj *CONST objv[] 251 ){ 252 if( objc!=1 ){ 253 Tcl_WrongNumArgs(interp, 1, objv, ""); 254 return TCL_ERROR; 255 } 256 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 257 return TCL_OK; 258 } 259 260 static int superlock_busy(void *pCtx, int nBusy){ 261 InterpAndScript *p = (InterpAndScript *)pCtx; 262 Tcl_Obj *pEval; /* Script to evaluate */ 263 int iVal = 0; /* Value to return */ 264 265 pEval = Tcl_DuplicateObj(p->pScript); 266 Tcl_IncrRefCount(pEval); 267 Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy)); 268 Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 269 Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal); 270 Tcl_DecrRefCount(pEval); 271 272 return iVal; 273 } 274 275 /* 276 ** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT 277 */ 278 static int superlock_cmd( 279 ClientData cd, 280 Tcl_Interp *interp, 281 int objc, 282 Tcl_Obj *CONST objv[] 283 ){ 284 void *pLock; /* Lock context */ 285 char *zPath; 286 char *zVfs = 0; 287 InterpAndScript busy = {0, 0}; 288 int (*xBusy)(void*,int) = 0; /* Busy handler callback */ 289 int rc; /* Return code from sqlite3demo_superlock() */ 290 291 if( objc<3 || objc>5 ){ 292 Tcl_WrongNumArgs( 293 interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?"); 294 return TCL_ERROR; 295 } 296 297 zPath = Tcl_GetString(objv[2]); 298 299 if( objc>3 ){ 300 zVfs = Tcl_GetString(objv[3]); 301 if( strlen(zVfs)==0 ) zVfs = 0; 302 } 303 if( objc>4 ){ 304 busy.interp = interp; 305 busy.pScript = objv[4]; 306 xBusy = superlock_busy; 307 } 308 309 rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock); 310 assert( rc==SQLITE_OK || pLock==0 ); 311 assert( rc!=SQLITE_OK || pLock!=0 ); 312 313 if( rc!=SQLITE_OK ){ 314 Tcl_ResetResult(interp); 315 Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0); 316 return TCL_ERROR; 317 } 318 319 Tcl_CreateObjCommand( 320 interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del 321 ); 322 Tcl_SetObjResult(interp, objv[1]); 323 return TCL_OK; 324 } 325 326 int SqliteSuperlock_Init(Tcl_Interp *interp){ 327 Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0); 328 return TCL_OK; 329 } 330 #endif 331