xref: /sqlite-3.40.0/src/test_superlock.c (revision 01a109e5)
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 
106   /* Obtain a pointer to the sqlite3_file object open on the main db file. */
107   rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
108   if( rc!=SQLITE_OK ) return rc;
109 
110   /* Obtain the "recovery" lock. Normally, this lock is only obtained by
111   ** clients running database recovery.
112   */
113   rc = superlockShmLock(fd, 2, 1, pBusy);
114   if( rc!=SQLITE_OK ) return rc;
115 
116   /* Zero the start of the first shared-memory page. This means that any
117   ** clients that open read or write transactions from this point on will
118   ** have to run recovery before proceeding. Since they need the "recovery"
119   ** lock that this process is holding to do that, no new read or write
120   ** transactions may now be opened. Nor can a checkpoint be run, for the
121   ** same reason.
122   */
123   rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
124   if( rc!=SQLITE_OK ) return rc;
125   memset((void *)p, 0, 32);
126 
127   /* Obtain exclusive locks on all the "read-lock" slots. Once these locks
128   ** are held, it is guaranteed that there are no active reader, writer or
129   ** checkpointer clients.
130   */
131   rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
132   return rc;
133 }
134 
135 /*
136 ** Obtain a superlock on the database file identified by zPath, using the
137 ** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
138 ** returned and output variable *ppLock is populated with an opaque handle
139 ** that may be used with sqlite3demo_superunlock() to release the lock.
140 **
141 ** If an error occurs, *ppLock is set to 0 and an SQLite error code
142 ** (e.g. SQLITE_BUSY) is returned.
143 **
144 ** If a required lock cannot be obtained immediately and the xBusy parameter
145 ** to this function is not NULL, then xBusy is invoked in the same way
146 ** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
147 ** until either the lock can be obtained or the busy-handler function returns
148 ** 0 (indicating "give up").
149 */
150 int sqlite3demo_superlock(
151   const char *zPath,              /* Path to database file to lock */
152   const char *zVfs,               /* VFS to use to access database file */
153   int (*xBusy)(void*,int),        /* Busy handler callback */
154   void *pBusyArg,                 /* Context arg for busy handler */
155   void **ppLock                   /* OUT: Context to pass to superunlock() */
156 ){
157   sqlite3 *db = 0;                /* Database handle open on zPath */
158   SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
159   int rc;                         /* Return code */
160 
161   /* Open a database handle on the file to superlock. */
162   rc = sqlite3_open_v2(
163       zPath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
164   );
165 
166   /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
167   ** a WAL database, this is all we need to do.
168   **
169   ** A wrapper function is used to invoke the busy-handler instead of
170   ** registering the busy-handler function supplied by the user directly
171   ** with SQLite. This is because the same busy-handler function may be
172   ** invoked directly later on when attempting to obtain the extra locks
173   ** required in WAL mode. By using the wrapper, we are able to guarantee
174   ** that the "nBusy" integer parameter passed to the users busy-handler
175   ** represents the total number of busy-handler invocations made within
176   ** this call to sqlite3demo_superlock(), including any made during the
177   ** "BEGIN EXCLUSIVE".
178   */
179   if( rc==SQLITE_OK ){
180     busy.xBusy = xBusy;
181     busy.pBusyArg = pBusyArg;
182     sqlite3_busy_handler(db, superlockBusyHandler, (void *)&busy);
183     rc = sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
184   }
185 
186   /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
187   ** database, call superlockWalLock() to obtain the extra locks required
188   ** to prevent readers, writers and/or checkpointers from accessing the
189   ** db while this process is holding the superlock.
190   **
191   ** Before attempting any WAL locks, commit the transaction started above
192   ** to drop the WAL read and write locks currently held. Otherwise, the
193   ** new WAL locks may conflict with the old.
194   */
195   if( rc==SQLITE_OK ){
196     int bWal;                     /* True for a WAL database, false otherwise */
197     if( SQLITE_OK==(rc = superlockIsWal(db, &bWal)) && bWal ){
198       rc = sqlite3_exec(db, "COMMIT", 0, 0, 0);
199       if( rc==SQLITE_OK ){
200         rc = superlockWalLock(db, &busy);
201       }
202     }
203   }
204 
205   if( rc!=SQLITE_OK ){
206     sqlite3_close(db);
207     *ppLock = 0;
208   }else{
209     *ppLock = (void *)db;
210   }
211 
212   return rc;
213 }
214 
215 /*
216 ** Release a superlock held on a database file. The argument passed to
217 ** this function must have been obtained from a successful call to
218 ** sqlite3demo_superlock().
219 */
220 void sqlite3demo_superunlock(void *pLock){
221   sqlite3_close((sqlite3 *)pLock);
222 }
223 
224 /*
225 ** End of example code. Everything below here is the test harness.
226 **************************************************************************
227 **************************************************************************
228 *************************************************************************/
229 
230 
231 #ifdef SQLITE_TEST
232 
233 #include <tcl.h>
234 
235 struct InterpAndScript {
236   Tcl_Interp *interp;
237   Tcl_Obj *pScript;
238 };
239 typedef struct InterpAndScript InterpAndScript;
240 
241 static void superunlock_del(ClientData cd){
242   sqlite3demo_superunlock((void *)cd);
243 }
244 
245 static int superunlock_cmd(
246   ClientData cd,
247   Tcl_Interp *interp,
248   int objc,
249   Tcl_Obj *CONST objv[]
250 ){
251   if( objc!=1 ){
252     Tcl_WrongNumArgs(interp, 1, objv, "");
253     return TCL_ERROR;
254   }
255   Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
256   return TCL_OK;
257 }
258 
259 static int superlock_busy(void *pCtx, int nBusy){
260   InterpAndScript *p = (InterpAndScript *)pCtx;
261   Tcl_Obj *pEval;                 /* Script to evaluate */
262   int iVal = 0;                   /* Value to return */
263 
264   pEval = Tcl_DuplicateObj(p->pScript);
265   Tcl_IncrRefCount(pEval);
266   Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
267   Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
268   Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
269   Tcl_DecrRefCount(pEval);
270 
271   return iVal;
272 }
273 
274 /*
275 ** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
276 */
277 static int superlock_cmd(
278   ClientData cd,
279   Tcl_Interp *interp,
280   int objc,
281   Tcl_Obj *CONST objv[]
282 ){
283   void *pLock;                    /* Lock context */
284   char *zPath;
285   char *zVfs = 0;
286   InterpAndScript busy = {0, 0};
287   int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
288   int rc;                         /* Return code from sqlite3demo_superlock() */
289 
290   if( objc<3 || objc>5 ){
291     Tcl_WrongNumArgs(
292         interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
293     return TCL_ERROR;
294   }
295 
296   zPath = Tcl_GetString(objv[2]);
297 
298   if( objc>3 ){
299     zVfs = Tcl_GetString(objv[3]);
300     if( strlen(zVfs)==0 ) zVfs = 0;
301   }
302   if( objc>4 ){
303     busy.interp = interp;
304     busy.pScript = objv[4];
305     xBusy = superlock_busy;
306   }
307 
308   rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
309   assert( rc==SQLITE_OK || pLock==0 );
310   assert( rc!=SQLITE_OK || pLock!=0 );
311 
312   if( rc!=SQLITE_OK ){
313     extern const char *sqlite3ErrStr(int);
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