OLD | NEW |
(Empty) | |
| 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 ** An instance of the following structure is allocated for each active |
| 38 ** superlock. The opaque handle returned by sqlite3demo_superlock() is |
| 39 ** actually a pointer to an instance of this structure. |
| 40 */ |
| 41 struct Superlock { |
| 42 sqlite3 *db; /* Database handle used to lock db */ |
| 43 int bWal; /* True if db is a WAL database */ |
| 44 }; |
| 45 typedef struct Superlock Superlock; |
| 46 |
| 47 /* |
| 48 ** The pCtx pointer passed to this function is actually a pointer to a |
| 49 ** SuperlockBusy structure. Invoke the busy-handler function encapsulated |
| 50 ** by the structure and return the result. |
| 51 */ |
| 52 static 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 the |
| 61 ** 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 *pbWal |
| 65 ** is undefined in this case. |
| 66 */ |
| 67 static 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 idx |
| 87 ** of the file fd. If the lock cannot be obtained immediately, invoke |
| 88 ** the busy-handler until either it is obtained or the busy-handler |
| 89 ** callback returns 0. |
| 90 */ |
| 91 static 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 */ |
| 109 static 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 by |
| 122 ** 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 any |
| 128 ** clients that open read or write transactions from this point on will |
| 129 ** 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 write |
| 131 ** transactions may now be opened. Nor can a checkpoint be run, for the |
| 132 ** 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 locks |
| 139 ** 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 to |
| 149 ** sqlite3demo_superlock(). |
| 150 */ |
| 151 void 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 the |
| 169 ** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is |
| 170 ** returned and output variable *ppLock is populated with an opaque handle |
| 171 ** 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 parameter |
| 177 ** to this function is not NULL, then xBusy is invoked in the same way |
| 178 ** as a busy-handler registered with SQLite (using sqlite3_busy_handler()) |
| 179 ** until either the lock can be obtained or the busy-handler function returns |
| 180 ** 0 (indicating "give up"). |
| 181 */ |
| 182 int 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, zVfs |
| 200 ); |
| 201 |
| 202 /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not |
| 203 ** a WAL database, this is all we need to do. |
| 204 ** |
| 205 ** A wrapper function is used to invoke the busy-handler instead of |
| 206 ** registering the busy-handler function supplied by the user directly |
| 207 ** with SQLite. This is because the same busy-handler function may be |
| 208 ** invoked directly later on when attempting to obtain the extra locks |
| 209 ** required in WAL mode. By using the wrapper, we are able to guarantee |
| 210 ** that the "nBusy" integer parameter passed to the users busy-handler |
| 211 ** represents the total number of busy-handler invocations made within |
| 212 ** this call to sqlite3demo_superlock(), including any made during the |
| 213 ** "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 WAL |
| 223 ** database, call superlockWalLock() to obtain the extra locks required |
| 224 ** to prevent readers, writers and/or checkpointers from accessing the |
| 225 ** db while this process is holding the superlock. |
| 226 ** |
| 227 ** Before attempting any WAL locks, commit the transaction started above |
| 228 ** to drop the WAL read and write locks currently held. Otherwise, the |
| 229 ** 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_TEST |
| 258 |
| 259 #include <tcl.h> |
| 260 |
| 261 struct InterpAndScript { |
| 262 Tcl_Interp *interp; |
| 263 Tcl_Obj *pScript; |
| 264 }; |
| 265 typedef struct InterpAndScript InterpAndScript; |
| 266 |
| 267 static void superunlock_del(ClientData cd){ |
| 268 sqlite3demo_superunlock((void *)cd); |
| 269 } |
| 270 |
| 271 static int 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 |
| 285 static 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-SCRIPT |
| 302 */ |
| 303 static int 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), 0); |
| 342 return TCL_ERROR; |
| 343 } |
| 344 |
| 345 Tcl_CreateObjCommand( |
| 346 interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del |
| 347 ); |
| 348 Tcl_SetObjResult(interp, objv[1]); |
| 349 return TCL_OK; |
| 350 } |
| 351 |
| 352 int SqliteSuperlock_Init(Tcl_Interp *interp){ |
| 353 Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0); |
| 354 return TCL_OK; |
| 355 } |
| 356 #endif |
OLD | NEW |