xref: /sqlite-3.40.0/src/test_superlock.c (revision c216eee7)
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 flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
155     sqlite3_file *fd = 0;
156     sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
157     fd->pMethods->xShmLock(fd, 2, SQLITE_SHM_NLOCK-2, flags);
158   }
159   sqlite3_close(p->db);
160   sqlite3_free(p);
161 }
162 
163 /*
164 ** Obtain a superlock on the database file identified by zPath, using the
165 ** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
166 ** returned and output variable *ppLock is populated with an opaque handle
167 ** that may be used with sqlite3demo_superunlock() to release the lock.
168 **
169 ** If an error occurs, *ppLock is set to 0 and an SQLite error code
170 ** (e.g. SQLITE_BUSY) is returned.
171 **
172 ** If a required lock cannot be obtained immediately and the xBusy parameter
173 ** to this function is not NULL, then xBusy is invoked in the same way
174 ** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
175 ** until either the lock can be obtained or the busy-handler function returns
176 ** 0 (indicating "give up").
177 */
178 int sqlite3demo_superlock(
179   const char *zPath,              /* Path to database file to lock */
180   const char *zVfs,               /* VFS to use to access database file */
181   int (*xBusy)(void*,int),        /* Busy handler callback */
182   void *pBusyArg,                 /* Context arg for busy handler */
183   void **ppLock                   /* OUT: Context to pass to superunlock() */
184 ){
185   SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
186   int rc;                         /* Return code */
187   Superlock *pLock;
188 
189   pLock = sqlite3_malloc(sizeof(Superlock));
190   if( !pLock ) return SQLITE_NOMEM;
191   memset(pLock, 0, sizeof(Superlock));
192 
193   /* Open a database handle on the file to superlock. */
194   rc = sqlite3_open_v2(
195       zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
196   );
197 
198   /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
199   ** a WAL database, this is all we need to do.
200   **
201   ** A wrapper function is used to invoke the busy-handler instead of
202   ** registering the busy-handler function supplied by the user directly
203   ** with SQLite. This is because the same busy-handler function may be
204   ** invoked directly later on when attempting to obtain the extra locks
205   ** required in WAL mode. By using the wrapper, we are able to guarantee
206   ** that the "nBusy" integer parameter passed to the users busy-handler
207   ** represents the total number of busy-handler invocations made within
208   ** this call to sqlite3demo_superlock(), including any made during the
209   ** "BEGIN EXCLUSIVE".
210   */
211   if( rc==SQLITE_OK ){
212     busy.xBusy = xBusy;
213     busy.pBusyArg = pBusyArg;
214     sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy);
215     rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0);
216   }
217 
218   /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
219   ** database, call superlockWalLock() to obtain the extra locks required
220   ** to prevent readers, writers and/or checkpointers from accessing the
221   ** db while this process is holding the superlock.
222   **
223   ** Before attempting any WAL locks, commit the transaction started above
224   ** to drop the WAL read and write locks currently held. Otherwise, the
225   ** new WAL locks may conflict with the old.
226   */
227   if( rc==SQLITE_OK ){
228     if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){
229       rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0);
230       if( rc==SQLITE_OK ){
231         rc = superlockWalLock(pLock->db, &busy);
232       }
233     }
234   }
235 
236   if( rc!=SQLITE_OK ){
237     sqlite3demo_superunlock(pLock);
238     *ppLock = 0;
239   }else{
240     *ppLock = pLock;
241   }
242 
243   return rc;
244 }
245 
246 /*
247 ** End of example code. Everything below here is the test harness.
248 **************************************************************************
249 **************************************************************************
250 *************************************************************************/
251 
252 
253 #ifdef SQLITE_TEST
254 
255 #include <tcl.h>
256 
257 struct InterpAndScript {
258   Tcl_Interp *interp;
259   Tcl_Obj *pScript;
260 };
261 typedef struct InterpAndScript InterpAndScript;
262 
263 static void superunlock_del(ClientData cd){
264   sqlite3demo_superunlock((void *)cd);
265 }
266 
267 static int superunlock_cmd(
268   ClientData cd,
269   Tcl_Interp *interp,
270   int objc,
271   Tcl_Obj *CONST objv[]
272 ){
273   if( objc!=1 ){
274     Tcl_WrongNumArgs(interp, 1, objv, "");
275     return TCL_ERROR;
276   }
277   Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
278   return TCL_OK;
279 }
280 
281 static int superlock_busy(void *pCtx, int nBusy){
282   InterpAndScript *p = (InterpAndScript *)pCtx;
283   Tcl_Obj *pEval;                 /* Script to evaluate */
284   int iVal = 0;                   /* Value to return */
285 
286   pEval = Tcl_DuplicateObj(p->pScript);
287   Tcl_IncrRefCount(pEval);
288   Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
289   Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
290   Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
291   Tcl_DecrRefCount(pEval);
292 
293   return iVal;
294 }
295 
296 /*
297 ** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
298 */
299 static int superlock_cmd(
300   ClientData cd,
301   Tcl_Interp *interp,
302   int objc,
303   Tcl_Obj *CONST objv[]
304 ){
305   void *pLock;                    /* Lock context */
306   char *zPath;
307   char *zVfs = 0;
308   InterpAndScript busy = {0, 0};
309   int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
310   int rc;                         /* Return code from sqlite3demo_superlock() */
311 
312   if( objc<3 || objc>5 ){
313     Tcl_WrongNumArgs(
314         interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
315     return TCL_ERROR;
316   }
317 
318   zPath = Tcl_GetString(objv[2]);
319 
320   if( objc>3 ){
321     zVfs = Tcl_GetString(objv[3]);
322     if( strlen(zVfs)==0 ) zVfs = 0;
323   }
324   if( objc>4 ){
325     busy.interp = interp;
326     busy.pScript = objv[4];
327     xBusy = superlock_busy;
328   }
329 
330   rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
331   assert( rc==SQLITE_OK || pLock==0 );
332   assert( rc!=SQLITE_OK || pLock!=0 );
333 
334   if( rc!=SQLITE_OK ){
335     extern const char *sqlite3ErrStr(int);
336     Tcl_ResetResult(interp);
337     Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
338     return TCL_ERROR;
339   }
340 
341   Tcl_CreateObjCommand(
342       interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
343   );
344   Tcl_SetObjResult(interp, objv[1]);
345   return TCL_OK;
346 }
347 
348 int SqliteSuperlock_Init(Tcl_Interp *interp){
349   Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
350   return TCL_OK;
351 }
352 #endif
353