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 */
superlockBusyHandler(void * pCtx,int UNUSED)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 */
superlockIsWal(Superlock * pLock)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 */
superlockShmLock(sqlite3_file * fd,int idx,int nByte,SuperlockBusy * pBusy)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 */
superlockWalLock(sqlite3 * db,SuperlockBusy * pBusy)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 */
sqlite3demo_superunlock(void * pLock)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 */
sqlite3demo_superlock(const char * zPath,const char * zVfs,int (* xBusy)(void *,int),void * pBusyArg,void ** ppLock)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
superunlock_del(ClientData cd)267 static void superunlock_del(ClientData cd){
268 sqlite3demo_superunlock((void *)cd);
269 }
270
superunlock_cmd(ClientData cd,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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
superlock_busy(void * pCtx,int nBusy)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 */
superlock_cmd(ClientData cd,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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
SqliteSuperlock_Init(Tcl_Interp * interp)352 int SqliteSuperlock_Init(Tcl_Interp *interp){
353 Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
354 return TCL_OK;
355 }
356 #endif
357