Annotation of embedaddon/sqlite3/src/test_superlock.c, revision 1.1
1.1 ! misho 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
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>