modernc.org/cc@v1.0.1/v2/testdata/_sqlite/src/notify.c (about)

     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    sqlite3ErrorWithMsg(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