AryaWu/sqlite
0
1/*2** 2009 March 33**4** The author disclaims copyright to this source code. In place of5** 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**13** This file contains the implementation of the sqlite3_unlock_notify()14** API method and its associated functionality.15*/16#include "sqliteInt.h"17#include "btreeInt.h"18 19/* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */20#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY21 22/*23** Public interfaces:24**25** sqlite3ConnectionBlocked()26** sqlite3ConnectionUnlocked()27** sqlite3ConnectionClosed()28** sqlite3_unlock_notify()29*/30 31#define assertMutexHeld() \32 assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)) )33 34/*35** Head of a linked list of all sqlite3 objects created by this process36** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection37** is not NULL. This variable may only accessed while the STATIC_MAIN38** mutex is held.39*/40static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;41 42#ifndef NDEBUG43/*44** This function is a complex assert() that verifies the following 45** properties of the blocked connections list:46**47** 1) Each entry in the list has a non-NULL value for either 48** pUnlockConnection or pBlockingConnection, or both.49**50** 2) All entries in the list that share a common value for 51** xUnlockNotify are grouped together.52**53** 3) If the argument db is not NULL, then none of the entries in the54** blocked connections list have pUnlockConnection or pBlockingConnection55** set to db. This is used when closing connection db.56*/57static void checkListProperties(sqlite3 *db){58 sqlite3 *p;59 for(p=sqlite3BlockedList; p; p=p->pNextBlocked){60 int seen = 0;61 sqlite3 *p2;62 63 /* Verify property (1) */64 assert( p->pUnlockConnection || p->pBlockingConnection );65 66 /* Verify property (2) */67 for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){68 if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;69 assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );70 assert( db==0 || p->pUnlockConnection!=db );71 assert( db==0 || p->pBlockingConnection!=db );72 }73 }74}75#else76# define checkListProperties(x)77#endif78 79/*80** Remove connection db from the blocked connections list. If connection81** db is not currently a part of the list, this function is a no-op.82*/83static void removeFromBlockedList(sqlite3 *db){84 sqlite3 **pp;85 assertMutexHeld();86 for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){87 if( *pp==db ){88 *pp = (*pp)->pNextBlocked;89 break;90 }91 }92}93 94/*95** Add connection db to the blocked connections list. It is assumed96** that it is not already a part of the list.97*/98static void addToBlockedList(sqlite3 *db){99 sqlite3 **pp;100 assertMutexHeld();101 for(102 pp=&sqlite3BlockedList; 103 *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify; 104 pp=&(*pp)->pNextBlocked105 );106 db->pNextBlocked = *pp;107 *pp = db;108}109 110/*111** Obtain the STATIC_MAIN mutex.112*/113static void enterMutex(void){114 sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN));115 checkListProperties(0);116}117 118/*119** Release the STATIC_MAIN mutex.120*/121static void leaveMutex(void){122 assertMutexHeld();123 checkListProperties(0);124 sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN));125}126 127/*128** Register an unlock-notify callback.129**130** This is called after connection "db" has attempted some operation131** but has received an SQLITE_LOCKED error because another connection132** (call it pOther) in the same process was busy using the same shared133** cache. pOther is found by looking at db->pBlockingConnection.134**135** If there is no blocking connection, the callback is invoked immediately,136** before this routine returns.137**138** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate139** a deadlock.140**141** Otherwise, make arrangements to invoke xNotify when pOther drops142** its locks.143**144** Each call to this routine overrides any prior callbacks registered145** on the same "db". If xNotify==0 then any prior callbacks are immediately146** cancelled.147*/148int sqlite3_unlock_notify(149 sqlite3 *db,150 void (*xNotify)(void **, int),151 void *pArg152){153 int rc = SQLITE_OK;154 155#ifdef SQLITE_ENABLE_API_ARMOR156 if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;157#endif158 sqlite3_mutex_enter(db->mutex);159 enterMutex();160 161 if( xNotify==0 ){162 removeFromBlockedList(db);163 db->pBlockingConnection = 0;164 db->pUnlockConnection = 0;165 db->xUnlockNotify = 0;166 db->pUnlockArg = 0;167 }else if( 0==db->pBlockingConnection ){168 /* The blocking transaction has been concluded. Or there never was a 169 ** blocking transaction. In either case, invoke the notify callback170 ** immediately. 171 */172 xNotify(&pArg, 1);173 }else{174 sqlite3 *p;175 176 for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}177 if( p ){178 rc = SQLITE_LOCKED; /* Deadlock detected. */179 }else{180 db->pUnlockConnection = db->pBlockingConnection;181 db->xUnlockNotify = xNotify;182 db->pUnlockArg = pArg;183 removeFromBlockedList(db);184 addToBlockedList(db);185 }186 }187 188 leaveMutex();189 assert( !db->mallocFailed );190 sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0));191 sqlite3_mutex_leave(db->mutex);192 return rc;193}194 195/*196** This function is called while stepping or preparing a statement 197** associated with connection db. The operation will return SQLITE_LOCKED198** to the user because it requires a lock that will not be available199** until connection pBlocker concludes its current transaction.200*/201void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){202 enterMutex();203 if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){204 addToBlockedList(db);205 }206 db->pBlockingConnection = pBlocker;207 leaveMutex();208}209 210/*211** This function is called when212** the transaction opened by database db has just finished. Locks held 213** by database connection db have been released.214**215** This function loops through each entry in the blocked connections216** list and does the following:217**218** 1) If the sqlite3.pBlockingConnection member of a list entry is219** set to db, then set pBlockingConnection=0.220**221** 2) If the sqlite3.pUnlockConnection member of a list entry is222** set to db, then invoke the configured unlock-notify callback and223** set pUnlockConnection=0.224**225** 3) If the two steps above mean that pBlockingConnection==0 and226** pUnlockConnection==0, remove the entry from the blocked connections227** list.228*/229void sqlite3ConnectionUnlocked(sqlite3 *db){230 void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */231 int nArg = 0; /* Number of entries in aArg[] */232 sqlite3 **pp; /* Iterator variable */233 void **aArg; /* Arguments to the unlock callback */234 void **aDyn = 0; /* Dynamically allocated space for aArg[] */235 void *aStatic[16]; /* Starter space for aArg[]. No malloc required */236 237 aArg = aStatic;238 enterMutex(); /* Enter STATIC_MAIN mutex */239 240 /* This loop runs once for each entry in the blocked-connections list. */241 for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){242 sqlite3 *p = *pp;243 244 /* Step 1. */245 if( p->pBlockingConnection==db ){246 p->pBlockingConnection = 0;247 }248 249 /* Step 2. */250 if( p->pUnlockConnection==db ){251 assert( p->xUnlockNotify );252 if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){253 xUnlockNotify(aArg, nArg);254 nArg = 0;255 }256 257 sqlite3BeginBenignMalloc();258 assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );259 assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn );260 if( (!aDyn && nArg==(int)ArraySize(aStatic))261 || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*)))262 ){263 /* The aArg[] array needs to grow. */264 void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);265 if( pNew ){266 memcpy(pNew, aArg, nArg*sizeof(void *));267 sqlite3_free(aDyn);268 aDyn = aArg = pNew;269 }else{270 /* This occurs when the array of context pointers that need to271 ** be passed to the unlock-notify callback is larger than the272 ** aStatic[] array allocated on the stack and the attempt to 273 ** allocate a larger array from the heap has failed.274 **275 ** This is a difficult situation to handle. Returning an error276 ** code to the caller is insufficient, as even if an error code277 ** is returned the transaction on connection db will still be278 ** closed and the unlock-notify callbacks on blocked connections279 ** will go unissued. This might cause the application to wait280 ** indefinitely for an unlock-notify callback that will never 281 ** arrive.282 **283 ** Instead, invoke the unlock-notify callback with the context284 ** array already accumulated. We can then clear the array and285 ** begin accumulating any further context pointers without 286 ** requiring any dynamic allocation. This is sub-optimal because287 ** it means that instead of one callback with a large array of288 ** context pointers the application will receive two or more289 ** callbacks with smaller arrays of context pointers, which will290 ** reduce the applications ability to prioritize multiple 291 ** connections. But it is the best that can be done under the292 ** circumstances.293 */294 xUnlockNotify(aArg, nArg);295 nArg = 0;296 }297 }298 sqlite3EndBenignMalloc();299 300 aArg[nArg++] = p->pUnlockArg;301 xUnlockNotify = p->xUnlockNotify;302 p->pUnlockConnection = 0;303 p->xUnlockNotify = 0;304 p->pUnlockArg = 0;305 }306 307 /* Step 3. */308 if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){309 /* Remove connection p from the blocked connections list. */310 *pp = p->pNextBlocked;311 p->pNextBlocked = 0;312 }else{313 pp = &p->pNextBlocked;314 }315 }316 317 if( nArg!=0 ){318 xUnlockNotify(aArg, nArg);319 }320 sqlite3_free(aDyn);321 leaveMutex(); /* Leave STATIC_MAIN mutex */322}323 324/*325** This is called when the database connection passed as an argument is 326** being closed. The connection is removed from the blocked list.327*/328void sqlite3ConnectionClosed(sqlite3 *db){329 sqlite3ConnectionUnlocked(db);330 enterMutex();331 removeFromBlockedList(db);332 checkListProperties(db);333 leaveMutex();334}335#endif336 