Annotation of embedaddon/sqlite3/src/notify.c, revision 1.1
1.1 ! misho 1: /*
! 2: ** 2009 March 3
! 3: **
! 4: ** The author disclaims copyright to this source code. In place of
! 5: ** 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_NOTIFY
! 21:
! 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_MASTER)) )
! 33:
! 34: /*
! 35: ** Head of a linked list of all sqlite3 objects created by this process
! 36: ** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
! 37: ** is not NULL. This variable may only accessed while the STATIC_MASTER
! 38: ** mutex is held.
! 39: */
! 40: static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
! 41:
! 42: #ifndef NDEBUG
! 43: /*
! 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 the
! 54: ** blocked connections list have pUnlockConnection or pBlockingConnection
! 55: ** set to db. This is used when closing connection db.
! 56: */
! 57: static 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: #else
! 76: # define checkListProperties(x)
! 77: #endif
! 78:
! 79: /*
! 80: ** Remove connection db from the blocked connections list. If connection
! 81: ** db is not currently a part of the list, this function is a no-op.
! 82: */
! 83: static 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 assumed
! 96: ** that it is not already a part of the list.
! 97: */
! 98: static void addToBlockedList(sqlite3 *db){
! 99: sqlite3 **pp;
! 100: assertMutexHeld();
! 101: for(
! 102: pp=&sqlite3BlockedList;
! 103: *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify;
! 104: pp=&(*pp)->pNextBlocked
! 105: );
! 106: db->pNextBlocked = *pp;
! 107: *pp = db;
! 108: }
! 109:
! 110: /*
! 111: ** Obtain the STATIC_MASTER mutex.
! 112: */
! 113: static void enterMutex(void){
! 114: sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
! 115: checkListProperties(0);
! 116: }
! 117:
! 118: /*
! 119: ** Release the STATIC_MASTER mutex.
! 120: */
! 121: static void leaveMutex(void){
! 122: assertMutexHeld();
! 123: checkListProperties(0);
! 124: sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
! 125: }
! 126:
! 127: /*
! 128: ** Register an unlock-notify callback.
! 129: **
! 130: ** This is called after connection "db" has attempted some operation
! 131: ** but has received an SQLITE_LOCKED error because another connection
! 132: ** (call it pOther) in the same process was busy using the same shared
! 133: ** 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 indicate
! 139: ** a deadlock.
! 140: **
! 141: ** Otherwise, make arrangements to invoke xNotify when pOther drops
! 142: ** its locks.
! 143: **
! 144: ** Each call to this routine overrides any prior callbacks registered
! 145: ** on the same "db". If xNotify==0 then any prior callbacks are immediately
! 146: ** cancelled.
! 147: */
! 148: int sqlite3_unlock_notify(
! 149: sqlite3 *db,
! 150: void (*xNotify)(void **, int),
! 151: void *pArg
! 152: ){
! 153: int rc = SQLITE_OK;
! 154:
! 155: sqlite3_mutex_enter(db->mutex);
! 156: enterMutex();
! 157:
! 158: if( xNotify==0 ){
! 159: removeFromBlockedList(db);
! 160: db->pBlockingConnection = 0;
! 161: db->pUnlockConnection = 0;
! 162: db->xUnlockNotify = 0;
! 163: db->pUnlockArg = 0;
! 164: }else if( 0==db->pBlockingConnection ){
! 165: /* The blocking transaction has been concluded. Or there never was a
! 166: ** blocking transaction. In either case, invoke the notify callback
! 167: ** immediately.
! 168: */
! 169: xNotify(&pArg, 1);
! 170: }else{
! 171: sqlite3 *p;
! 172:
! 173: for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}
! 174: if( p ){
! 175: rc = SQLITE_LOCKED; /* Deadlock detected. */
! 176: }else{
! 177: db->pUnlockConnection = db->pBlockingConnection;
! 178: db->xUnlockNotify = xNotify;
! 179: db->pUnlockArg = pArg;
! 180: removeFromBlockedList(db);
! 181: addToBlockedList(db);
! 182: }
! 183: }
! 184:
! 185: leaveMutex();
! 186: assert( !db->mallocFailed );
! 187: sqlite3Error(db, rc, (rc?"database is deadlocked":0));
! 188: sqlite3_mutex_leave(db->mutex);
! 189: return rc;
! 190: }
! 191:
! 192: /*
! 193: ** This function is called while stepping or preparing a statement
! 194: ** associated with connection db. The operation will return SQLITE_LOCKED
! 195: ** to the user because it requires a lock that will not be available
! 196: ** until connection pBlocker concludes its current transaction.
! 197: */
! 198: void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
! 199: enterMutex();
! 200: if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
! 201: addToBlockedList(db);
! 202: }
! 203: db->pBlockingConnection = pBlocker;
! 204: leaveMutex();
! 205: }
! 206:
! 207: /*
! 208: ** This function is called when
! 209: ** the transaction opened by database db has just finished. Locks held
! 210: ** by database connection db have been released.
! 211: **
! 212: ** This function loops through each entry in the blocked connections
! 213: ** list and does the following:
! 214: **
! 215: ** 1) If the sqlite3.pBlockingConnection member of a list entry is
! 216: ** set to db, then set pBlockingConnection=0.
! 217: **
! 218: ** 2) If the sqlite3.pUnlockConnection member of a list entry is
! 219: ** set to db, then invoke the configured unlock-notify callback and
! 220: ** set pUnlockConnection=0.
! 221: **
! 222: ** 3) If the two steps above mean that pBlockingConnection==0 and
! 223: ** pUnlockConnection==0, remove the entry from the blocked connections
! 224: ** list.
! 225: */
! 226: void sqlite3ConnectionUnlocked(sqlite3 *db){
! 227: void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
! 228: int nArg = 0; /* Number of entries in aArg[] */
! 229: sqlite3 **pp; /* Iterator variable */
! 230: void **aArg; /* Arguments to the unlock callback */
! 231: void **aDyn = 0; /* Dynamically allocated space for aArg[] */
! 232: void *aStatic[16]; /* Starter space for aArg[]. No malloc required */
! 233:
! 234: aArg = aStatic;
! 235: enterMutex(); /* Enter STATIC_MASTER mutex */
! 236:
! 237: /* This loop runs once for each entry in the blocked-connections list. */
! 238: for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
! 239: sqlite3 *p = *pp;
! 240:
! 241: /* Step 1. */
! 242: if( p->pBlockingConnection==db ){
! 243: p->pBlockingConnection = 0;
! 244: }
! 245:
! 246: /* Step 2. */
! 247: if( p->pUnlockConnection==db ){
! 248: assert( p->xUnlockNotify );
! 249: if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){
! 250: xUnlockNotify(aArg, nArg);
! 251: nArg = 0;
! 252: }
! 253:
! 254: sqlite3BeginBenignMalloc();
! 255: assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
! 256: assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn );
! 257: if( (!aDyn && nArg==(int)ArraySize(aStatic))
! 258: || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*)))
! 259: ){
! 260: /* The aArg[] array needs to grow. */
! 261: void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
! 262: if( pNew ){
! 263: memcpy(pNew, aArg, nArg*sizeof(void *));
! 264: sqlite3_free(aDyn);
! 265: aDyn = aArg = pNew;
! 266: }else{
! 267: /* This occurs when the array of context pointers that need to
! 268: ** be passed to the unlock-notify callback is larger than the
! 269: ** aStatic[] array allocated on the stack and the attempt to
! 270: ** allocate a larger array from the heap has failed.
! 271: **
! 272: ** This is a difficult situation to handle. Returning an error
! 273: ** code to the caller is insufficient, as even if an error code
! 274: ** is returned the transaction on connection db will still be
! 275: ** closed and the unlock-notify callbacks on blocked connections
! 276: ** will go unissued. This might cause the application to wait
! 277: ** indefinitely for an unlock-notify callback that will never
! 278: ** arrive.
! 279: **
! 280: ** Instead, invoke the unlock-notify callback with the context
! 281: ** array already accumulated. We can then clear the array and
! 282: ** begin accumulating any further context pointers without
! 283: ** requiring any dynamic allocation. This is sub-optimal because
! 284: ** it means that instead of one callback with a large array of
! 285: ** context pointers the application will receive two or more
! 286: ** callbacks with smaller arrays of context pointers, which will
! 287: ** reduce the applications ability to prioritize multiple
! 288: ** connections. But it is the best that can be done under the
! 289: ** circumstances.
! 290: */
! 291: xUnlockNotify(aArg, nArg);
! 292: nArg = 0;
! 293: }
! 294: }
! 295: sqlite3EndBenignMalloc();
! 296:
! 297: aArg[nArg++] = p->pUnlockArg;
! 298: xUnlockNotify = p->xUnlockNotify;
! 299: p->pUnlockConnection = 0;
! 300: p->xUnlockNotify = 0;
! 301: p->pUnlockArg = 0;
! 302: }
! 303:
! 304: /* Step 3. */
! 305: if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){
! 306: /* Remove connection p from the blocked connections list. */
! 307: *pp = p->pNextBlocked;
! 308: p->pNextBlocked = 0;
! 309: }else{
! 310: pp = &p->pNextBlocked;
! 311: }
! 312: }
! 313:
! 314: if( nArg!=0 ){
! 315: xUnlockNotify(aArg, nArg);
! 316: }
! 317: sqlite3_free(aDyn);
! 318: leaveMutex(); /* Leave STATIC_MASTER mutex */
! 319: }
! 320:
! 321: /*
! 322: ** This is called when the database connection passed as an argument is
! 323: ** being closed. The connection is removed from the blocked list.
! 324: */
! 325: void sqlite3ConnectionClosed(sqlite3 *db){
! 326: sqlite3ConnectionUnlocked(db);
! 327: enterMutex();
! 328: removeFromBlockedList(db);
! 329: checkListProperties(db);
! 330: leaveMutex();
! 331: }
! 332: #endif
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>