Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
notify.c336 linesDownload Raw Back to src
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