4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
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.
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:
17 ** sqlite3demo_superlock()
18 ** sqlite3demo_superunlock()
22 #include <string.h> /* memset(), strlen() */
23 #include <assert.h> /* assert() */
26 ** A structure to collect a busy-handler callback and argument and a count
27 ** of the number of times it has been invoked.
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 */
34 typedef struct SuperlockBusy SuperlockBusy
;
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.
42 sqlite3
*db
; /* Database handle used to lock db */
43 int bWal
; /* True if db is a WAL database */
45 typedef struct Superlock Superlock
;
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.
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
++);
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.
64 ** If an error occurs, return an SQLite error code. The value of *pbWal
65 ** is undefined in this case.
67 static int superlockIsWal(Superlock
*pLock
){
68 int rc
; /* Return Code */
69 sqlite3_stmt
*pStmt
; /* Compiled PRAGMA journal_mode statement */
71 rc
= sqlite3_prepare(pLock
->db
, "PRAGMA main.journal_mode", -1, &pStmt
, 0);
72 if( rc
!=SQLITE_OK
) return rc
;
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 ){
82 return sqlite3_finalize(pStmt
);
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.
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 */
98 int (*xShmLock
)(sqlite3_file
*, int, int, int) = fd
->pMethods
->xShmLock
;
100 rc
= xShmLock(fd
, idx
, nByte
, SQLITE_SHM_LOCK
|SQLITE_SHM_EXCLUSIVE
);
101 }while( rc
==SQLITE_BUSY
&& superlockBusyHandler((void *)pBusy
, 0) );
106 ** Obtain the extra locks on the database file required for WAL databases.
107 ** Invoke the supplied busy-handler as required.
109 static int superlockWalLock(
110 sqlite3
*db
, /* Database handle open on WAL database */
111 SuperlockBusy
*pBusy
/* Busy handler wrapper object */
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 */
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
;
121 /* Obtain the "recovery" lock. Normally, this lock is only obtained by
122 ** clients running database recovery.
124 rc
= superlockShmLock(fd
, 2, 1, pBusy
);
125 if( rc
!=SQLITE_OK
) return rc
;
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
134 rc
= fd
->pMethods
->xShmMap(fd
, 0, 32*1024, 1, &p
);
135 if( rc
!=SQLITE_OK
) return rc
;
136 memset((void *)p
, 0, 32);
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.
142 rc
= superlockShmLock(fd
, 3, SQLITE_SHM_NLOCK
-3, pBusy
);
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().
151 void sqlite3demo_superunlock(void *pLock
){
152 Superlock
*p
= (Superlock
*)pLock
;
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
);
159 fd
->pMethods
->xShmLock(fd
, 2, 1, flags
);
160 fd
->pMethods
->xShmLock(fd
, 3, SQLITE_SHM_NLOCK
-3, flags
);
163 sqlite3_close(p
->db
);
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.
173 ** If an error occurs, *ppLock is set to 0 and an SQLite error code
174 ** (e.g. SQLITE_BUSY) is returned.
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").
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() */
189 SuperlockBusy busy
= {0, 0, 0}; /* Busy handler wrapper object */
190 int rc
; /* Return code */
193 pLock
= sqlite3_malloc(sizeof(Superlock
));
194 if( !pLock
) return SQLITE_NOMEM
;
195 memset(pLock
, 0, sizeof(Superlock
));
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
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.
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".
217 busy
.pBusyArg
= pBusyArg
;
218 sqlite3_busy_handler(pLock
->db
, superlockBusyHandler
, (void *)&busy
);
219 rc
= sqlite3_exec(pLock
->db
, "BEGIN EXCLUSIVE", 0, 0, 0);
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.
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.
232 if( SQLITE_OK
==(rc
= superlockIsWal(pLock
)) && pLock
->bWal
){
233 rc
= sqlite3_exec(pLock
->db
, "COMMIT", 0, 0, 0);
235 rc
= superlockWalLock(pLock
->db
, &busy
);
241 sqlite3demo_superunlock(pLock
);
251 ** End of example code. Everything below here is the test harness.
252 **************************************************************************
253 **************************************************************************
254 *************************************************************************/
261 struct InterpAndScript
{
265 typedef struct InterpAndScript InterpAndScript
;
267 static void superunlock_del(ClientData cd
){
268 sqlite3demo_superunlock((void *)cd
);
271 static int superunlock_cmd(
275 Tcl_Obj
*CONST objv
[]
278 Tcl_WrongNumArgs(interp
, 1, objv
, "");
281 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
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 */
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
);
301 ** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
303 static int superlock_cmd(
307 Tcl_Obj
*CONST objv
[]
309 void *pLock
; /* Lock context */
312 InterpAndScript busy
= {0, 0};
313 int (*xBusy
)(void*,int) = 0; /* Busy handler callback */
314 int rc
; /* Return code from sqlite3demo_superlock() */
316 if( objc
<3 || objc
>5 ){
318 interp
, 1, objv
, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
322 zPath
= Tcl_GetString(objv
[2]);
325 zVfs
= Tcl_GetString(objv
[3]);
326 if( strlen(zVfs
)==0 ) zVfs
= 0;
329 busy
.interp
= interp
;
330 busy
.pScript
= objv
[4];
331 xBusy
= superlock_busy
;
334 rc
= sqlite3demo_superlock(zPath
, zVfs
, xBusy
, &busy
, &pLock
);
335 assert( rc
==SQLITE_OK
|| pLock
==0 );
336 assert( rc
!=SQLITE_OK
|| pLock
!=0 );
339 extern const char *sqlite3ErrStr(int);
340 Tcl_ResetResult(interp
);
341 Tcl_AppendResult(interp
, sqlite3ErrStr(rc
), 0);
345 Tcl_CreateObjCommand(
346 interp
, Tcl_GetString(objv
[1]), superunlock_cmd
, pLock
, superunlock_del
348 Tcl_SetObjResult(interp
, objv
[1]);
352 int SqliteSuperlock_Init(Tcl_Interp
*interp
){
353 Tcl_CreateObjCommand(interp
, "sqlite3demo_superlock", superlock_cmd
, 0, 0);