wasCSharpSQLite – Blame information for rev
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | using System.Diagnostics; |
2 | |||
3 | namespace Community.CsharpSqlite |
||
4 | { |
||
5 | public partial class Sqlite3 |
||
6 | { |
||
7 | /* |
||
8 | ** 2009 March 3 |
||
9 | ** |
||
10 | ** The author disclaims copyright to this source code. In place of |
||
11 | ** a legal notice, here is a blessing: |
||
12 | ** |
||
13 | ** May you do good and not evil. |
||
14 | ** May you find forgiveness for yourself and forgive others. |
||
15 | ** May you share freely, never taking more than you give. |
||
16 | ** |
||
17 | ************************************************************************* |
||
18 | ** |
||
19 | ** This file contains the implementation of the sqlite3_unlock_notify() |
||
20 | ** API method and its associated functionality. |
||
21 | ************************************************************************* |
||
22 | ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart |
||
23 | ** C#-SQLite is an independent reimplementation of the SQLite software library |
||
24 | ** |
||
25 | ** SQLITE_SOURCE_ID: 2009-12-07 16:39:13 1ed88e9d01e9eda5cbc622e7614277f29bcc551c |
||
26 | ** |
||
27 | ************************************************************************* |
||
28 | */ |
||
29 | //#include "sqliteInt.h" |
||
30 | //#include "btreeInt.h" |
||
31 | |||
32 | /* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */ |
||
33 | #if SQLITE_ENABLE_UNLOCK_NOTIFY |
||
34 | |||
35 | /* |
||
36 | ** Public interfaces: |
||
37 | ** |
||
38 | ** sqlite3ConnectionBlocked() |
||
39 | ** sqlite3ConnectionUnlocked() |
||
40 | ** sqlite3ConnectionClosed() |
||
41 | ** sqlite3_unlock_notify() |
||
42 | */ |
||
43 | |||
44 | //#define assertMutexHeld() \ |
||
45 | assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) ) |
||
46 | |||
47 | /* |
||
48 | ** Head of a linked list of all sqlite3 objects created by this process |
||
49 | ** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection |
||
50 | ** is not NULL. This variable may only accessed while the STATIC_MASTER |
||
51 | ** mutex is held. |
||
52 | */ |
||
53 | static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0; |
||
54 | |||
55 | #if !NDEBUG |
||
56 | /* |
||
57 | ** This function is a complex assert() that verifies the following |
||
58 | ** properties of the blocked connections list: |
||
59 | ** |
||
60 | ** 1) Each entry in the list has a non-NULL value for either |
||
61 | ** pUnlockConnection or pBlockingConnection, or both. |
||
62 | ** |
||
63 | ** 2) All entries in the list that share a common value for |
||
64 | ** xUnlockNotify are grouped together. |
||
65 | ** |
||
66 | ** 3) If the argument db is not NULL, then none of the entries in the |
||
67 | ** blocked connections list have pUnlockConnection or pBlockingConnection |
||
68 | ** set to db. This is used when closing connection db. |
||
69 | */ |
||
70 | static void checkListProperties(sqlite3 *db){ |
||
71 | sqlite3 *p; |
||
72 | for(p=sqlite3BlockedList; p; p=p->pNextBlocked){ |
||
73 | int seen = 0; |
||
74 | sqlite3 *p2; |
||
75 | |||
76 | /* Verify property (1) */ |
||
77 | assert( p->pUnlockConnection || p->pBlockingConnection ); |
||
78 | |||
79 | /* Verify property (2) */ |
||
80 | for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){ |
||
81 | if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1; |
||
82 | assert( p2->xUnlockNotify==p->xUnlockNotify || !seen ); |
||
83 | assert( db==0 || p->pUnlockConnection!=db ); |
||
84 | assert( db==0 || p->pBlockingConnection!=db ); |
||
85 | } |
||
86 | } |
||
87 | } |
||
88 | #else |
||
89 | //# define checkListProperties(x) |
||
90 | #endif |
||
91 | |||
92 | /* |
||
93 | ** Remove connection db from the blocked connections list. If connection |
||
94 | ** db is not currently a part of the list, this function is a no-op. |
||
95 | */ |
||
96 | static void removeFromBlockedList(sqlite3 *db){ |
||
97 | sqlite3 **pp; |
||
98 | assertMutexHeld(); |
||
99 | for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){ |
||
100 | if( *pp==db ){ |
||
101 | *pp = (*pp)->pNextBlocked; |
||
102 | break; |
||
103 | } |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /* |
||
108 | ** Add connection db to the blocked connections list. It is assumed |
||
109 | ** that it is not already a part of the list. |
||
110 | */ |
||
111 | static void addToBlockedList(sqlite3 *db){ |
||
112 | sqlite3 **pp; |
||
113 | assertMutexHeld(); |
||
114 | for( |
||
115 | pp=&sqlite3BlockedList; |
||
116 | *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify; |
||
117 | pp=&(*pp)->pNextBlocked |
||
118 | ); |
||
119 | db->pNextBlocked = *pp; |
||
120 | *pp = db; |
||
121 | } |
||
122 | |||
123 | /* |
||
124 | ** Obtain the STATIC_MASTER mutex. |
||
125 | */ |
||
126 | static void enterMutex(){ |
||
127 | sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); |
||
128 | checkListProperties(0); |
||
129 | } |
||
130 | |||
131 | /* |
||
132 | ** Release the STATIC_MASTER mutex. |
||
133 | */ |
||
134 | static void leaveMutex(){ |
||
135 | assertMutexHeld(); |
||
136 | checkListProperties(0); |
||
137 | sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); |
||
138 | } |
||
139 | |||
140 | /* |
||
141 | ** Register an unlock-notify callback. |
||
142 | ** |
||
143 | ** This is called after connection "db" has attempted some operation |
||
144 | ** but has received an SQLITE_LOCKED error because another connection |
||
145 | ** (call it pOther) in the same process was busy using the same shared |
||
146 | ** cache. pOther is found by looking at db->pBlockingConnection. |
||
147 | ** |
||
148 | ** If there is no blocking connection, the callback is invoked immediately, |
||
149 | ** before this routine returns. |
||
150 | ** |
||
151 | ** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate |
||
152 | ** a deadlock. |
||
153 | ** |
||
154 | ** Otherwise, make arrangements to invoke xNotify when pOther drops |
||
155 | ** its locks. |
||
156 | ** |
||
157 | ** Each call to this routine overrides any prior callbacks registered |
||
158 | ** on the same "db". If xNotify==0 then any prior callbacks are immediately |
||
159 | ** cancelled. |
||
160 | */ |
||
161 | int sqlite3_unlock_notify( |
||
162 | sqlite3 *db, |
||
163 | void (*xNotify)(void **, int), |
||
164 | void *pArg |
||
165 | ){ |
||
166 | int rc = SQLITE_OK; |
||
167 | |||
168 | sqlite3_mutex_enter(db->mutex); |
||
169 | enterMutex(); |
||
170 | |||
171 | if( xNotify==0 ){ |
||
172 | removeFromBlockedList(db); |
||
173 | db->pUnlockConnection = 0; |
||
174 | db->xUnlockNotify = 0; |
||
175 | db->pUnlockArg = 0; |
||
176 | }else if( 0==db->pBlockingConnection ){ |
||
177 | /* The blocking transaction has been concluded. Or there never was a |
||
178 | ** blocking transaction. In either case, invoke the notify callback |
||
179 | ** immediately. |
||
180 | */ |
||
181 | xNotify(&pArg, 1); |
||
182 | }else{ |
||
183 | sqlite3 *p; |
||
184 | |||
185 | for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){} |
||
186 | if( p ){ |
||
187 | rc = SQLITE_LOCKED; /* Deadlock detected. */ |
||
188 | }else{ |
||
189 | db->pUnlockConnection = db->pBlockingConnection; |
||
190 | db->xUnlockNotify = xNotify; |
||
191 | db->pUnlockArg = pArg; |
||
192 | removeFromBlockedList(db); |
||
193 | addToBlockedList(db); |
||
194 | } |
||
195 | } |
||
196 | |||
197 | leaveMutex(); |
||
198 | assert( !db->mallocFailed ); |
||
199 | sqlite3Error(db, rc, (rc?"database is deadlocked":0)); |
||
200 | sqlite3_mutex_leave(db->mutex); |
||
201 | return rc; |
||
202 | } |
||
203 | |||
204 | /* |
||
205 | ** This function is called while stepping or preparing a statement |
||
206 | ** associated with connection db. The operation will return SQLITE_LOCKED |
||
207 | ** to the user because it requires a lock that will not be available |
||
208 | ** until connection pBlocker concludes its current transaction. |
||
209 | */ |
||
210 | void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){ |
||
211 | enterMutex(); |
||
212 | if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){ |
||
213 | addToBlockedList(db); |
||
214 | } |
||
215 | db->pBlockingConnection = pBlocker; |
||
216 | leaveMutex(); |
||
217 | } |
||
218 | |||
219 | /* |
||
220 | ** This function is called when |
||
221 | ** the transaction opened by database db has just finished. Locks held |
||
222 | ** by database connection db have been released. |
||
223 | ** |
||
224 | ** This function loops through each entry in the blocked connections |
||
225 | ** list and does the following: |
||
226 | ** |
||
227 | ** 1) If the sqlite3.pBlockingConnection member of a list entry is |
||
228 | ** set to db, then set pBlockingConnection=0. |
||
229 | ** |
||
230 | ** 2) If the sqlite3.pUnlockConnection member of a list entry is |
||
231 | ** set to db, then invoke the configured unlock-notify callback and |
||
232 | ** set pUnlockConnection=0. |
||
233 | ** |
||
234 | ** 3) If the two steps above mean that pBlockingConnection==0 and |
||
235 | ** pUnlockConnection==0, remove the entry from the blocked connections |
||
236 | ** list. |
||
237 | */ |
||
238 | void sqlite3ConnectionUnlocked(sqlite3 *db){ |
||
239 | void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */ |
||
240 | int nArg = 0; /* Number of entries in aArg[] */ |
||
241 | sqlite3 **pp; /* Iterator variable */ |
||
242 | void **aArg; /* Arguments to the unlock callback */ |
||
243 | void **aDyn = 0; /* Dynamically allocated space for aArg[] */ |
||
244 | void *aStatic[16]; /* Starter space for aArg[]. No malloc required */ |
||
245 | |||
246 | aArg = aStatic; |
||
247 | enterMutex(); /* Enter STATIC_MASTER mutex */ |
||
248 | |||
249 | /* This loop runs once for each entry in the blocked-connections list. */ |
||
250 | for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){ |
||
251 | sqlite3 *p = *pp; |
||
252 | |||
253 | /* Step 1. */ |
||
254 | if( p->pBlockingConnection==db ){ |
||
255 | p->pBlockingConnection = 0; |
||
256 | } |
||
257 | |||
258 | /* Step 2. */ |
||
259 | if( p->pUnlockConnection==db ){ |
||
260 | assert( p->xUnlockNotify ); |
||
261 | if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){ |
||
262 | xUnlockNotify(aArg, nArg); |
||
263 | nArg = 0; |
||
264 | } |
||
265 | |||
266 | sqlite3BeginBenignMalloc(); |
||
267 | assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) ); |
||
268 | assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn ); |
||
269 | if( (!aDyn && nArg==(int)ArraySize(aStatic)) |
||
270 | || (aDyn && nArg==(int)(sqlite3DbMallocSize(db, aDyn)/sizeof(void*))) |
||
271 | ){ |
||
272 | /* The aArg[] array needs to grow. */ |
||
273 | void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2); |
||
274 | if( pNew ){ |
||
275 | memcpy(pNew, aArg, nArg*sizeof(void *)); |
||
276 | //sqlite3_free(aDyn); |
||
277 | aDyn = aArg = pNew; |
||
278 | }else{ |
||
279 | /* This occurs when the array of context pointers that need to |
||
280 | ** be passed to the unlock-notify callback is larger than the |
||
281 | ** aStatic[] array allocated on the stack and the attempt to |
||
282 | ** allocate a larger array from the heap has failed. |
||
283 | ** |
||
284 | ** This is a difficult situation to handle. Returning an error |
||
285 | ** code to the caller is insufficient, as even if an error code |
||
286 | ** is returned the transaction on connection db will still be |
||
287 | ** closed and the unlock-notify callbacks on blocked connections |
||
288 | ** will go unissued. This might cause the application to wait |
||
289 | ** indefinitely for an unlock-notify callback that will never |
||
290 | ** arrive. |
||
291 | ** |
||
292 | ** Instead, invoke the unlock-notify callback with the context |
||
293 | ** array already accumulated. We can then clear the array and |
||
294 | ** begin accumulating any further context pointers without |
||
295 | ** requiring any dynamic allocation. This is sub-optimal because |
||
296 | ** it means that instead of one callback with a large array of |
||
297 | ** context pointers the application will receive two or more |
||
298 | ** callbacks with smaller arrays of context pointers, which will |
||
299 | ** reduce the applications ability to prioritize multiple |
||
300 | ** connections. But it is the best that can be done under the |
||
301 | ** circumstances. |
||
302 | */ |
||
303 | xUnlockNotify(aArg, nArg); |
||
304 | nArg = 0; |
||
305 | } |
||
306 | } |
||
307 | sqlite3EndBenignMalloc(); |
||
308 | |||
309 | aArg[nArg++] = p->pUnlockArg; |
||
310 | xUnlockNotify = p->xUnlockNotify; |
||
311 | p->pUnlockConnection = 0; |
||
312 | p->xUnlockNotify = 0; |
||
313 | p->pUnlockArg = 0; |
||
314 | } |
||
315 | |||
316 | /* Step 3. */ |
||
317 | if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){ |
||
318 | /* Remove connection p from the blocked connections list. */ |
||
319 | *pp = p->pNextBlocked; |
||
320 | p->pNextBlocked = 0; |
||
321 | }else{ |
||
322 | pp = &p->pNextBlocked; |
||
323 | } |
||
324 | } |
||
325 | |||
326 | if( nArg!=0 ){ |
||
327 | xUnlockNotify(aArg, nArg); |
||
328 | } |
||
329 | //sqlite3_free(aDyn); |
||
330 | leaveMutex(); /* Leave STATIC_MASTER mutex */ |
||
331 | } |
||
332 | |||
333 | /* |
||
334 | ** This is called when the database connection passed as an argument is |
||
335 | ** being closed. The connection is removed from the blocked list. |
||
336 | */ |
||
337 | void sqlite3ConnectionClosed(sqlite3 *db){ |
||
338 | sqlite3ConnectionUnlocked(db); |
||
339 | enterMutex(); |
||
340 | removeFromBlockedList(db); |
||
341 | checkListProperties(db); |
||
342 | leaveMutex(); |
||
343 | } |
||
344 | #endif |
||
345 | } |
||
346 | } |