xref: /sqlite-3.40.0/src/notify.c (revision ccb2113a)
1404ca075Sdanielk1977 /*
2404ca075Sdanielk1977 ** 2009 March 3
3404ca075Sdanielk1977 **
4404ca075Sdanielk1977 ** The author disclaims copyright to this source code.  In place of
5404ca075Sdanielk1977 ** a legal notice, here is a blessing:
6404ca075Sdanielk1977 **
7404ca075Sdanielk1977 **    May you do good and not evil.
8404ca075Sdanielk1977 **    May you find forgiveness for yourself and forgive others.
9404ca075Sdanielk1977 **    May you share freely, never taking more than you give.
10404ca075Sdanielk1977 **
11404ca075Sdanielk1977 *************************************************************************
12404ca075Sdanielk1977 **
13404ca075Sdanielk1977 ** This file contains the implementation of the sqlite3_unlock_notify()
14404ca075Sdanielk1977 ** API method and its associated functionality.
15404ca075Sdanielk1977 */
16404ca075Sdanielk1977 #include "sqliteInt.h"
17404ca075Sdanielk1977 #include "btreeInt.h"
18404ca075Sdanielk1977 
19404ca075Sdanielk1977 /* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */
20404ca075Sdanielk1977 #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
21404ca075Sdanielk1977 
22404ca075Sdanielk1977 /*
23404ca075Sdanielk1977 ** Public interfaces:
24404ca075Sdanielk1977 **
25404ca075Sdanielk1977 **   sqlite3ConnectionBlocked()
26404ca075Sdanielk1977 **   sqlite3ConnectionUnlocked()
27404ca075Sdanielk1977 **   sqlite3ConnectionClosed()
28404ca075Sdanielk1977 **   sqlite3_unlock_notify()
29404ca075Sdanielk1977 */
30404ca075Sdanielk1977 
31404ca075Sdanielk1977 #define assertMutexHeld() \
32*ccb2113aSdrh   assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)) )
33404ca075Sdanielk1977 
34404ca075Sdanielk1977 /*
35404ca075Sdanielk1977 ** Head of a linked list of all sqlite3 objects created by this process
36404ca075Sdanielk1977 ** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
37*ccb2113aSdrh ** is not NULL. This variable may only accessed while the STATIC_MAIN
38404ca075Sdanielk1977 ** mutex is held.
39404ca075Sdanielk1977 */
40404ca075Sdanielk1977 static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
41404ca075Sdanielk1977 
42404ca075Sdanielk1977 #ifndef NDEBUG
43404ca075Sdanielk1977 /*
44404ca075Sdanielk1977 ** This function is a complex assert() that verifies the following
45404ca075Sdanielk1977 ** properties of the blocked connections list:
46404ca075Sdanielk1977 **
47404ca075Sdanielk1977 **   1) Each entry in the list has a non-NULL value for either
48404ca075Sdanielk1977 **      pUnlockConnection or pBlockingConnection, or both.
49404ca075Sdanielk1977 **
50404ca075Sdanielk1977 **   2) All entries in the list that share a common value for
51404ca075Sdanielk1977 **      xUnlockNotify are grouped together.
52404ca075Sdanielk1977 **
53404ca075Sdanielk1977 **   3) If the argument db is not NULL, then none of the entries in the
54404ca075Sdanielk1977 **      blocked connections list have pUnlockConnection or pBlockingConnection
55404ca075Sdanielk1977 **      set to db. This is used when closing connection db.
56404ca075Sdanielk1977 */
checkListProperties(sqlite3 * db)57404ca075Sdanielk1977 static void checkListProperties(sqlite3 *db){
58404ca075Sdanielk1977   sqlite3 *p;
59404ca075Sdanielk1977   for(p=sqlite3BlockedList; p; p=p->pNextBlocked){
60404ca075Sdanielk1977     int seen = 0;
61404ca075Sdanielk1977     sqlite3 *p2;
62404ca075Sdanielk1977 
63404ca075Sdanielk1977     /* Verify property (1) */
64404ca075Sdanielk1977     assert( p->pUnlockConnection || p->pBlockingConnection );
65404ca075Sdanielk1977 
66404ca075Sdanielk1977     /* Verify property (2) */
67404ca075Sdanielk1977     for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
68404ca075Sdanielk1977       if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;
69404ca075Sdanielk1977       assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );
70404ca075Sdanielk1977       assert( db==0 || p->pUnlockConnection!=db );
71404ca075Sdanielk1977       assert( db==0 || p->pBlockingConnection!=db );
72404ca075Sdanielk1977     }
73404ca075Sdanielk1977   }
74404ca075Sdanielk1977 }
75404ca075Sdanielk1977 #else
76404ca075Sdanielk1977 # define checkListProperties(x)
77404ca075Sdanielk1977 #endif
78404ca075Sdanielk1977 
79404ca075Sdanielk1977 /*
80404ca075Sdanielk1977 ** Remove connection db from the blocked connections list. If connection
81404ca075Sdanielk1977 ** db is not currently a part of the list, this function is a no-op.
82404ca075Sdanielk1977 */
removeFromBlockedList(sqlite3 * db)83404ca075Sdanielk1977 static void removeFromBlockedList(sqlite3 *db){
84404ca075Sdanielk1977   sqlite3 **pp;
85404ca075Sdanielk1977   assertMutexHeld();
86404ca075Sdanielk1977   for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
87404ca075Sdanielk1977     if( *pp==db ){
88404ca075Sdanielk1977       *pp = (*pp)->pNextBlocked;
89404ca075Sdanielk1977       break;
90404ca075Sdanielk1977     }
91404ca075Sdanielk1977   }
92404ca075Sdanielk1977 }
93404ca075Sdanielk1977 
94404ca075Sdanielk1977 /*
95404ca075Sdanielk1977 ** Add connection db to the blocked connections list. It is assumed
96404ca075Sdanielk1977 ** that it is not already a part of the list.
97404ca075Sdanielk1977 */
addToBlockedList(sqlite3 * db)98404ca075Sdanielk1977 static void addToBlockedList(sqlite3 *db){
99404ca075Sdanielk1977   sqlite3 **pp;
100404ca075Sdanielk1977   assertMutexHeld();
101404ca075Sdanielk1977   for(
102404ca075Sdanielk1977     pp=&sqlite3BlockedList;
103404ca075Sdanielk1977     *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify;
104404ca075Sdanielk1977     pp=&(*pp)->pNextBlocked
105404ca075Sdanielk1977   );
106404ca075Sdanielk1977   db->pNextBlocked = *pp;
107404ca075Sdanielk1977   *pp = db;
108404ca075Sdanielk1977 }
109404ca075Sdanielk1977 
110404ca075Sdanielk1977 /*
111*ccb2113aSdrh ** Obtain the STATIC_MAIN mutex.
112404ca075Sdanielk1977 */
enterMutex(void)11364aca191Sdanielk1977 static void enterMutex(void){
114*ccb2113aSdrh   sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN));
115404ca075Sdanielk1977   checkListProperties(0);
116404ca075Sdanielk1977 }
117404ca075Sdanielk1977 
118404ca075Sdanielk1977 /*
119*ccb2113aSdrh ** Release the STATIC_MAIN mutex.
120404ca075Sdanielk1977 */
leaveMutex(void)12164aca191Sdanielk1977 static void leaveMutex(void){
122404ca075Sdanielk1977   assertMutexHeld();
123404ca075Sdanielk1977   checkListProperties(0);
124*ccb2113aSdrh   sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN));
125404ca075Sdanielk1977 }
126404ca075Sdanielk1977 
127404ca075Sdanielk1977 /*
128404ca075Sdanielk1977 ** Register an unlock-notify callback.
12965a73badSdrh **
13065a73badSdrh ** This is called after connection "db" has attempted some operation
13165a73badSdrh ** but has received an SQLITE_LOCKED error because another connection
13265a73badSdrh ** (call it pOther) in the same process was busy using the same shared
13365a73badSdrh ** cache.  pOther is found by looking at db->pBlockingConnection.
13465a73badSdrh **
13565a73badSdrh ** If there is no blocking connection, the callback is invoked immediately,
13665a73badSdrh ** before this routine returns.
13765a73badSdrh **
13865a73badSdrh ** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate
13965a73badSdrh ** a deadlock.
14065a73badSdrh **
14165a73badSdrh ** Otherwise, make arrangements to invoke xNotify when pOther drops
14265a73badSdrh ** its locks.
14365a73badSdrh **
14465a73badSdrh ** Each call to this routine overrides any prior callbacks registered
14565a73badSdrh ** on the same "db".  If xNotify==0 then any prior callbacks are immediately
14665a73badSdrh ** cancelled.
147404ca075Sdanielk1977 */
sqlite3_unlock_notify(sqlite3 * db,void (* xNotify)(void **,int),void * pArg)148404ca075Sdanielk1977 int sqlite3_unlock_notify(
149404ca075Sdanielk1977   sqlite3 *db,
150404ca075Sdanielk1977   void (*xNotify)(void **, int),
151404ca075Sdanielk1977   void *pArg
152404ca075Sdanielk1977 ){
153404ca075Sdanielk1977   int rc = SQLITE_OK;
154404ca075Sdanielk1977 
155404ca075Sdanielk1977   sqlite3_mutex_enter(db->mutex);
156404ca075Sdanielk1977   enterMutex();
157404ca075Sdanielk1977 
15865a73badSdrh   if( xNotify==0 ){
15965a73badSdrh     removeFromBlockedList(db);
1604b93f6bdSshaneh     db->pBlockingConnection = 0;
16165a73badSdrh     db->pUnlockConnection = 0;
16265a73badSdrh     db->xUnlockNotify = 0;
16365a73badSdrh     db->pUnlockArg = 0;
16465a73badSdrh   }else if( 0==db->pBlockingConnection ){
165404ca075Sdanielk1977     /* The blocking transaction has been concluded. Or there never was a
166404ca075Sdanielk1977     ** blocking transaction. In either case, invoke the notify callback
167404ca075Sdanielk1977     ** immediately.
168404ca075Sdanielk1977     */
169404ca075Sdanielk1977     xNotify(&pArg, 1);
170404ca075Sdanielk1977   }else{
171404ca075Sdanielk1977     sqlite3 *p;
172404ca075Sdanielk1977 
17365a73badSdrh     for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}
174404ca075Sdanielk1977     if( p ){
175404ca075Sdanielk1977       rc = SQLITE_LOCKED;              /* Deadlock detected. */
176404ca075Sdanielk1977     }else{
177404ca075Sdanielk1977       db->pUnlockConnection = db->pBlockingConnection;
178404ca075Sdanielk1977       db->xUnlockNotify = xNotify;
179404ca075Sdanielk1977       db->pUnlockArg = pArg;
180404ca075Sdanielk1977       removeFromBlockedList(db);
181404ca075Sdanielk1977       addToBlockedList(db);
182404ca075Sdanielk1977     }
183404ca075Sdanielk1977   }
184404ca075Sdanielk1977 
185404ca075Sdanielk1977   leaveMutex();
186404ca075Sdanielk1977   assert( !db->mallocFailed );
18713f40da3Sdrh   sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0));
188404ca075Sdanielk1977   sqlite3_mutex_leave(db->mutex);
189404ca075Sdanielk1977   return rc;
190404ca075Sdanielk1977 }
191404ca075Sdanielk1977 
192404ca075Sdanielk1977 /*
193404ca075Sdanielk1977 ** This function is called while stepping or preparing a statement
194404ca075Sdanielk1977 ** associated with connection db. The operation will return SQLITE_LOCKED
195404ca075Sdanielk1977 ** to the user because it requires a lock that will not be available
196404ca075Sdanielk1977 ** until connection pBlocker concludes its current transaction.
197404ca075Sdanielk1977 */
sqlite3ConnectionBlocked(sqlite3 * db,sqlite3 * pBlocker)198404ca075Sdanielk1977 void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
199404ca075Sdanielk1977   enterMutex();
200335c0faaSdrh   if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
201404ca075Sdanielk1977     addToBlockedList(db);
202404ca075Sdanielk1977   }
203404ca075Sdanielk1977   db->pBlockingConnection = pBlocker;
204404ca075Sdanielk1977   leaveMutex();
205404ca075Sdanielk1977 }
206404ca075Sdanielk1977 
207404ca075Sdanielk1977 /*
20865a73badSdrh ** This function is called when
20965a73badSdrh ** the transaction opened by database db has just finished. Locks held
210404ca075Sdanielk1977 ** by database connection db have been released.
211404ca075Sdanielk1977 **
212404ca075Sdanielk1977 ** This function loops through each entry in the blocked connections
213404ca075Sdanielk1977 ** list and does the following:
214404ca075Sdanielk1977 **
215404ca075Sdanielk1977 **   1) If the sqlite3.pBlockingConnection member of a list entry is
216404ca075Sdanielk1977 **      set to db, then set pBlockingConnection=0.
217404ca075Sdanielk1977 **
218404ca075Sdanielk1977 **   2) If the sqlite3.pUnlockConnection member of a list entry is
219404ca075Sdanielk1977 **      set to db, then invoke the configured unlock-notify callback and
220404ca075Sdanielk1977 **      set pUnlockConnection=0.
221404ca075Sdanielk1977 **
222404ca075Sdanielk1977 **   3) If the two steps above mean that pBlockingConnection==0 and
223404ca075Sdanielk1977 **      pUnlockConnection==0, remove the entry from the blocked connections
224404ca075Sdanielk1977 **      list.
225404ca075Sdanielk1977 */
sqlite3ConnectionUnlocked(sqlite3 * db)226404ca075Sdanielk1977 void sqlite3ConnectionUnlocked(sqlite3 *db){
227404ca075Sdanielk1977   void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
228404ca075Sdanielk1977   int nArg = 0;                            /* Number of entries in aArg[] */
229404ca075Sdanielk1977   sqlite3 **pp;                            /* Iterator variable */
23065a73badSdrh   void **aArg;               /* Arguments to the unlock callback */
23165a73badSdrh   void **aDyn = 0;           /* Dynamically allocated space for aArg[] */
23265a73badSdrh   void *aStatic[16];         /* Starter space for aArg[].  No malloc required */
233404ca075Sdanielk1977 
23465a73badSdrh   aArg = aStatic;
235*ccb2113aSdrh   enterMutex();         /* Enter STATIC_MAIN mutex */
236404ca075Sdanielk1977 
237404ca075Sdanielk1977   /* This loop runs once for each entry in the blocked-connections list. */
238404ca075Sdanielk1977   for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
239404ca075Sdanielk1977     sqlite3 *p = *pp;
240404ca075Sdanielk1977 
241404ca075Sdanielk1977     /* Step 1. */
242404ca075Sdanielk1977     if( p->pBlockingConnection==db ){
243404ca075Sdanielk1977       p->pBlockingConnection = 0;
244404ca075Sdanielk1977     }
245404ca075Sdanielk1977 
246404ca075Sdanielk1977     /* Step 2. */
247404ca075Sdanielk1977     if( p->pUnlockConnection==db ){
248404ca075Sdanielk1977       assert( p->xUnlockNotify );
249404ca075Sdanielk1977       if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){
250404ca075Sdanielk1977         xUnlockNotify(aArg, nArg);
251404ca075Sdanielk1977         nArg = 0;
252404ca075Sdanielk1977       }
253404ca075Sdanielk1977 
254404ca075Sdanielk1977       sqlite3BeginBenignMalloc();
255404ca075Sdanielk1977       assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
2567c01f1d7Sdrh       assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn );
2577c01f1d7Sdrh       if( (!aDyn && nArg==(int)ArraySize(aStatic))
258b975598eSdrh        || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*)))
259404ca075Sdanielk1977       ){
260404ca075Sdanielk1977         /* The aArg[] array needs to grow. */
261404ca075Sdanielk1977         void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
262404ca075Sdanielk1977         if( pNew ){
263404ca075Sdanielk1977           memcpy(pNew, aArg, nArg*sizeof(void *));
264404ca075Sdanielk1977           sqlite3_free(aDyn);
265404ca075Sdanielk1977           aDyn = aArg = pNew;
266404ca075Sdanielk1977         }else{
267404ca075Sdanielk1977           /* This occurs when the array of context pointers that need to
268404ca075Sdanielk1977           ** be passed to the unlock-notify callback is larger than the
269404ca075Sdanielk1977           ** aStatic[] array allocated on the stack and the attempt to
270404ca075Sdanielk1977           ** allocate a larger array from the heap has failed.
271404ca075Sdanielk1977           **
272404ca075Sdanielk1977           ** This is a difficult situation to handle. Returning an error
273404ca075Sdanielk1977           ** code to the caller is insufficient, as even if an error code
274404ca075Sdanielk1977           ** is returned the transaction on connection db will still be
275404ca075Sdanielk1977           ** closed and the unlock-notify callbacks on blocked connections
276404ca075Sdanielk1977           ** will go unissued. This might cause the application to wait
277404ca075Sdanielk1977           ** indefinitely for an unlock-notify callback that will never
278404ca075Sdanielk1977           ** arrive.
279404ca075Sdanielk1977           **
280404ca075Sdanielk1977           ** Instead, invoke the unlock-notify callback with the context
281404ca075Sdanielk1977           ** array already accumulated. We can then clear the array and
282404ca075Sdanielk1977           ** begin accumulating any further context pointers without
283404ca075Sdanielk1977           ** requiring any dynamic allocation. This is sub-optimal because
284404ca075Sdanielk1977           ** it means that instead of one callback with a large array of
285404ca075Sdanielk1977           ** context pointers the application will receive two or more
286404ca075Sdanielk1977           ** callbacks with smaller arrays of context pointers, which will
287404ca075Sdanielk1977           ** reduce the applications ability to prioritize multiple
288404ca075Sdanielk1977           ** connections. But it is the best that can be done under the
289404ca075Sdanielk1977           ** circumstances.
290404ca075Sdanielk1977           */
291404ca075Sdanielk1977           xUnlockNotify(aArg, nArg);
292404ca075Sdanielk1977           nArg = 0;
293404ca075Sdanielk1977         }
294404ca075Sdanielk1977       }
295404ca075Sdanielk1977       sqlite3EndBenignMalloc();
296404ca075Sdanielk1977 
297404ca075Sdanielk1977       aArg[nArg++] = p->pUnlockArg;
298404ca075Sdanielk1977       xUnlockNotify = p->xUnlockNotify;
299404ca075Sdanielk1977       p->pUnlockConnection = 0;
300404ca075Sdanielk1977       p->xUnlockNotify = 0;
301404ca075Sdanielk1977       p->pUnlockArg = 0;
302404ca075Sdanielk1977     }
303404ca075Sdanielk1977 
304404ca075Sdanielk1977     /* Step 3. */
305335c0faaSdrh     if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){
306404ca075Sdanielk1977       /* Remove connection p from the blocked connections list. */
307404ca075Sdanielk1977       *pp = p->pNextBlocked;
308404ca075Sdanielk1977       p->pNextBlocked = 0;
309404ca075Sdanielk1977     }else{
310404ca075Sdanielk1977       pp = &p->pNextBlocked;
311404ca075Sdanielk1977     }
312404ca075Sdanielk1977   }
313404ca075Sdanielk1977 
314404ca075Sdanielk1977   if( nArg!=0 ){
315404ca075Sdanielk1977     xUnlockNotify(aArg, nArg);
316404ca075Sdanielk1977   }
317404ca075Sdanielk1977   sqlite3_free(aDyn);
318*ccb2113aSdrh   leaveMutex();         /* Leave STATIC_MAIN mutex */
319404ca075Sdanielk1977 }
320404ca075Sdanielk1977 
321404ca075Sdanielk1977 /*
322404ca075Sdanielk1977 ** This is called when the database connection passed as an argument is
323404ca075Sdanielk1977 ** being closed. The connection is removed from the blocked list.
324404ca075Sdanielk1977 */
sqlite3ConnectionClosed(sqlite3 * db)325404ca075Sdanielk1977 void sqlite3ConnectionClosed(sqlite3 *db){
326404ca075Sdanielk1977   sqlite3ConnectionUnlocked(db);
327404ca075Sdanielk1977   enterMutex();
328404ca075Sdanielk1977   removeFromBlockedList(db);
329404ca075Sdanielk1977   checkListProperties(db);
330404ca075Sdanielk1977   leaveMutex();
331404ca075Sdanielk1977 }
332404ca075Sdanielk1977 #endif
333