/* ** 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 ); sqlite3ErrorWithMsg(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