| Index: third_party/sqlite/src/src/notify.c
|
| diff --git a/third_party/sqlite/src/src/notify.c b/third_party/sqlite/src/src/notify.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fcab5bfaf01908d3de93fa88b5eae9e96ca3c8e8
|
| --- /dev/null
|
| +++ b/third_party/sqlite/src/src/notify.c
|
| @@ -0,0 +1,332 @@
|
| +/*
|
| +** 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
|
|
|