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