OLD | NEW |
| (Empty) |
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 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 */ | |
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 | |
267 static void superunlock_del(ClientData cd){ | |
268 sqlite3demo_superunlock((void *)cd); | |
269 } | |
270 | |
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 | |
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 */ | |
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 | |
352 int SqliteSuperlock_Init(Tcl_Interp *interp){ | |
353 Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0); | |
354 return TCL_OK; | |
355 } | |
356 #endif | |
OLD | NEW |