modernc.org/cc@v1.0.1/v2/testdata/_sqlite/src/test_superlock.c (about)

     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  #if defined(INCLUDE_SQLITE_TCL_H)
   260  #  include "sqlite_tcl.h"
   261  #else
   262  #  include "tcl.h"
   263  #  ifndef SQLITE_TCLAPI
   264  #    define SQLITE_TCLAPI
   265  #  endif
   266  #endif
   267  
   268  struct InterpAndScript {
   269    Tcl_Interp *interp;
   270    Tcl_Obj *pScript;
   271  };
   272  typedef struct InterpAndScript InterpAndScript;
   273  
   274  static void SQLITE_TCLAPI superunlock_del(ClientData cd){
   275    sqlite3demo_superunlock((void *)cd);
   276  }
   277  
   278  static int SQLITE_TCLAPI superunlock_cmd(
   279    ClientData cd,
   280    Tcl_Interp *interp,
   281    int objc,
   282    Tcl_Obj *CONST objv[]
   283  ){
   284    if( objc!=1 ){
   285      Tcl_WrongNumArgs(interp, 1, objv, "");
   286      return TCL_ERROR;
   287    }
   288    Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
   289    return TCL_OK;
   290  }
   291  
   292  static int superlock_busy(void *pCtx, int nBusy){
   293    InterpAndScript *p = (InterpAndScript *)pCtx;
   294    Tcl_Obj *pEval;                 /* Script to evaluate */
   295    int iVal = 0;                   /* Value to return */
   296  
   297    pEval = Tcl_DuplicateObj(p->pScript);
   298    Tcl_IncrRefCount(pEval);
   299    Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
   300    Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
   301    Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
   302    Tcl_DecrRefCount(pEval);
   303  
   304    return iVal;
   305  }
   306  
   307  /*
   308  ** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
   309  */
   310  static int SQLITE_TCLAPI superlock_cmd(
   311    ClientData cd,
   312    Tcl_Interp *interp,
   313    int objc,
   314    Tcl_Obj *CONST objv[]
   315  ){
   316    void *pLock;                    /* Lock context */
   317    char *zPath;
   318    char *zVfs = 0;
   319    InterpAndScript busy = {0, 0};
   320    int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
   321    int rc;                         /* Return code from sqlite3demo_superlock() */
   322  
   323    if( objc<3 || objc>5 ){
   324      Tcl_WrongNumArgs(
   325          interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
   326      return TCL_ERROR;
   327    }
   328  
   329    zPath = Tcl_GetString(objv[2]);
   330  
   331    if( objc>3 ){
   332      zVfs = Tcl_GetString(objv[3]);
   333      if( strlen(zVfs)==0 ) zVfs = 0;
   334    }
   335    if( objc>4 ){
   336      busy.interp = interp;
   337      busy.pScript = objv[4];
   338      xBusy = superlock_busy;
   339    }
   340  
   341    rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
   342    assert( rc==SQLITE_OK || pLock==0 );
   343    assert( rc!=SQLITE_OK || pLock!=0 );
   344  
   345    if( rc!=SQLITE_OK ){
   346      extern const char *sqlite3ErrStr(int);
   347      Tcl_ResetResult(interp);
   348      Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
   349      return TCL_ERROR;
   350    }
   351  
   352    Tcl_CreateObjCommand(
   353        interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
   354    );
   355    Tcl_SetObjResult(interp, objv[1]);
   356    return TCL_OK;
   357  }
   358  
   359  int SqliteSuperlock_Init(Tcl_Interp *interp){
   360    Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
   361    return TCL_OK;
   362  }
   363  #endif