| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- /*
- ** 2009 March 3
- **
- ** The author disclaims copyright to this source code. In place of
- ** a legal notice, here is a blessing:
- **
- ** May you do good and not evil.
- ** May you find forgiveness for yourself and forgive others.
- ** May you share freely, never taking more than you give.
- **
- *************************************************************************
- **
- ** This file contains the implementation of the sqlite3_unlock_notify()
- ** API method and its associated functionality.
- */
- #include "sqliteInt.h"
- #include "btreeInt.h"
- /* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */
- #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
- /*
- ** Public interfaces:
- **
- ** sqlite3ConnectionBlocked()
- ** sqlite3ConnectionUnlocked()
- ** sqlite3ConnectionClosed()
- ** sqlite3_unlock_notify()
- */
- #define assertMutexHeld() \
- assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) )
- /*
- ** Head of a linked list of all sqlite3 objects created by this process
- ** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
- ** is not NULL. This variable may only accessed while the STATIC_MASTER
- ** mutex is held.
- */
- static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
- #ifndef NDEBUG
- /*
- ** This function is a complex assert() that verifies the following
- ** properties of the blocked connections list:
- **
- ** 1) Each entry in the list has a non-NULL value for either
- ** pUnlockConnection or pBlockingConnection, or both.
- **
- ** 2) All entries in the list that share a common value for
- ** xUnlockNotify are grouped together.
- **
- ** 3) If the argument db is not NULL, then none of the entries in the
- ** blocked connections list have pUnlockConnection or pBlockingConnection
- ** set to db. This is used when closing connection db.
- */
- static void checkListProperties(sqlite3 *db){
- sqlite3 *p;
- for(p=sqlite3BlockedList; p; p=p->pNextBlocked){
- int seen = 0;
- sqlite3 *p2;
- /* Verify property (1) */
- assert( p->pUnlockConnection || p->pBlockingConnection );
- /* Verify property (2) */
- for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
- if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;
- assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );
- assert( db==0 || p->pUnlockConnection!=db );
- assert( db==0 || p->pBlockingConnection!=db );
- }
- }
- }
- #else
- # define checkListProperties(x)
- #endif
- /*
- ** Remove connection db from the blocked connections list. If connection
- ** db is not currently a part of the list, this function is a no-op.
- */
- static void removeFromBlockedList(sqlite3 *db){
- sqlite3 **pp;
- assertMutexHeld();
- for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
- if( *pp==db ){
- *pp = (*pp)->pNextBlocked;
- break;
- }
- }
- }
- /*
- ** Add connection db to the blocked connections list. It is assumed
- ** that it is not already a part of the list.
- */
- static void addToBlockedList(sqlite3 *db){
- sqlite3 **pp;
- assertMutexHeld();
- for(
- pp=&sqlite3BlockedList;
- *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify;
- pp=&(*pp)->pNextBlocked
- );
- db->pNextBlocked = *pp;
- *pp = db;
- }
- /*
- ** Obtain the STATIC_MASTER mutex.
- */
- static void enterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
- checkListProperties(0);
- }
- /*
- ** Release the STATIC_MASTER mutex.
- */
- static void leaveMutex(void){
- assertMutexHeld();
- checkListProperties(0);
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
- }
- /*
- ** Register an unlock-notify callback.
- **
- ** This is called after connection "db" has attempted some operation
- ** but has received an SQLITE_LOCKED error because another connection
- ** (call it pOther) in the same process was busy using the same shared
- ** cache. pOther is found by looking at db->pBlockingConnection.
- **
- ** If there is no blocking connection, the callback is invoked immediately,
- ** before this routine returns.
- **
- ** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate
- ** a deadlock.
- **
- ** Otherwise, make arrangements to invoke xNotify when pOther drops
- ** its locks.
- **
- ** Each call to this routine overrides any prior callbacks registered
- ** on the same "db". If xNotify==0 then any prior callbacks are immediately
- ** cancelled.
- */
- int sqlite3_unlock_notify(
- sqlite3 *db,
- void (*xNotify)(void **, int),
- void *pArg
- ){
- int rc = SQLITE_OK;
- sqlite3_mutex_enter(db->mutex);
- enterMutex();
- if( xNotify==0 ){
- removeFromBlockedList(db);
- db->pBlockingConnection = 0;
- db->pUnlockConnection = 0;
- db->xUnlockNotify = 0;
- db->pUnlockArg = 0;
- }else if( 0==db->pBlockingConnection ){
- /* The blocking transaction has been concluded. Or there never was a
- ** blocking transaction. In either case, invoke the notify callback
- ** immediately.
- */
- xNotify(&pArg, 1);
- }else{
- sqlite3 *p;
- for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}
- if( p ){
- rc = SQLITE_LOCKED; /* Deadlock detected. */
- }else{
- db->pUnlockConnection = db->pBlockingConnection;
- db->xUnlockNotify = xNotify;
- db->pUnlockArg = pArg;
- removeFromBlockedList(db);
- addToBlockedList(db);
- }
- }
- leaveMutex();
- assert( !db->mallocFailed );
- sqlite3Error(db, rc, (rc?"database is deadlocked":0));
- sqlite3_mutex_leave(db->mutex);
- return rc;
- }
- /*
- ** This function is called while stepping or preparing a statement
- ** associated with connection db. The operation will return SQLITE_LOCKED
- ** to the user because it requires a lock that will not be available
- ** until connection pBlocker concludes its current transaction.
- */
- void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
- enterMutex();
- if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
- addToBlockedList(db);
- }
- db->pBlockingConnection = pBlocker;
- leaveMutex();
- }
- /*
- ** This function is called when
- ** the transaction opened by database db has just finished. Locks held
- ** by database connection db have been released.
- **
- ** This function loops through each entry in the blocked connections
- ** list and does the following:
- **
- ** 1) If the sqlite3.pBlockingConnection member of a list entry is
- ** set to db, then set pBlockingConnection=0.
- **
- ** 2) If the sqlite3.pUnlockConnection member of a list entry is
- ** set to db, then invoke the configured unlock-notify callback and
- ** set pUnlockConnection=0.
- **
- ** 3) If the two steps above mean that pBlockingConnection==0 and
- ** pUnlockConnection==0, remove the entry from the blocked connections
- ** list.
- */
- void sqlite3ConnectionUnlocked(sqlite3 *db){
- void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
- int nArg = 0; /* Number of entries in aArg[] */
- sqlite3 **pp; /* Iterator variable */
- void **aArg; /* Arguments to the unlock callback */
- void **aDyn = 0; /* Dynamically allocated space for aArg[] */
- void *aStatic[16]; /* Starter space for aArg[]. No malloc required */
- aArg = aStatic;
- enterMutex(); /* Enter STATIC_MASTER mutex */
- /* This loop runs once for each entry in the blocked-connections list. */
- for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
- sqlite3 *p = *pp;
- /* Step 1. */
- if( p->pBlockingConnection==db ){
- p->pBlockingConnection = 0;
- }
- /* Step 2. */
- if( p->pUnlockConnection==db ){
- assert( p->xUnlockNotify );
- if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){
- xUnlockNotify(aArg, nArg);
- nArg = 0;
- }
- sqlite3BeginBenignMalloc();
- assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
- assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn );
- if( (!aDyn && nArg==(int)ArraySize(aStatic))
- || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*)))
- ){
- /* The aArg[] array needs to grow. */
- void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
- if( pNew ){
- memcpy(pNew, aArg, nArg*sizeof(void *));
- sqlite3_free(aDyn);
- aDyn = aArg = pNew;
- }else{
- /* This occurs when the array of context pointers that need to
- ** be passed to the unlock-notify callback is larger than the
- ** aStatic[] array allocated on the stack and the attempt to
- ** allocate a larger array from the heap has failed.
- **
- ** This is a difficult situation to handle. Returning an error
- ** code to the caller is insufficient, as even if an error code
- ** is returned the transaction on connection db will still be
- ** closed and the unlock-notify callbacks on blocked connections
- ** will go unissued. This might cause the application to wait
- ** indefinitely for an unlock-notify callback that will never
- ** arrive.
- **
- ** Instead, invoke the unlock-notify callback with the context
- ** array already accumulated. We can then clear the array and
- ** begin accumulating any further context pointers without
- ** requiring any dynamic allocation. This is sub-optimal because
- ** it means that instead of one callback with a large array of
- ** context pointers the application will receive two or more
- ** callbacks with smaller arrays of context pointers, which will
- ** reduce the applications ability to prioritize multiple
- ** connections. But it is the best that can be done under the
- ** circumstances.
- */
- xUnlockNotify(aArg, nArg);
- nArg = 0;
- }
- }
- sqlite3EndBenignMalloc();
- aArg[nArg++] = p->pUnlockArg;
- xUnlockNotify = p->xUnlockNotify;
- p->pUnlockConnection = 0;
- p->xUnlockNotify = 0;
- p->pUnlockArg = 0;
- }
- /* Step 3. */
- if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){
- /* Remove connection p from the blocked connections list. */
- *pp = p->pNextBlocked;
- p->pNextBlocked = 0;
- }else{
- pp = &p->pNextBlocked;
- }
- }
- if( nArg!=0 ){
- xUnlockNotify(aArg, nArg);
- }
- sqlite3_free(aDyn);
- leaveMutex(); /* Leave STATIC_MASTER mutex */
- }
- /*
- ** This is called when the database connection passed as an argument is
- ** being closed. The connection is removed from the blocked list.
- */
- void sqlite3ConnectionClosed(sqlite3 *db){
- sqlite3ConnectionUnlocked(db);
- enterMutex();
- removeFromBlockedList(db);
- checkListProperties(db);
- leaveMutex();
- }
- #endif
|