danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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. |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 | */ |
danielk1977 | 64aca19 | 2009-04-07 11:21:28 +0000 | [diff] [blame] | 113 | static void enterMutex(void){ |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 114 | sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); |
| 115 | checkListProperties(0); |
| 116 | } |
| 117 | |
| 118 | /* |
| 119 | ** Release the STATIC_MASTER mutex. |
| 120 | */ |
danielk1977 | 64aca19 | 2009-04-07 11:21:28 +0000 | [diff] [blame] | 121 | static void leaveMutex(void){ |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 122 | assertMutexHeld(); |
| 123 | checkListProperties(0); |
| 124 | sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); |
| 125 | } |
| 126 | |
| 127 | /* |
| 128 | ** Register an unlock-notify callback. |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 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. |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 | |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 158 | if( xNotify==0 ){ |
| 159 | removeFromBlockedList(db); |
shaneh | 4b93f6b | 2010-04-16 22:05:31 +0000 | [diff] [blame] | 160 | db->pBlockingConnection = 0; |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 161 | db->pUnlockConnection = 0; |
| 162 | db->xUnlockNotify = 0; |
| 163 | db->pUnlockArg = 0; |
| 164 | }else if( 0==db->pBlockingConnection ){ |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 | |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 173 | for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){} |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 ); |
drh | 13f40da | 2014-08-22 18:00:11 +0000 | [diff] [blame] | 187 | sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0)); |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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(); |
drh | 335c0fa | 2010-04-17 16:10:19 +0000 | [diff] [blame] | 200 | if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){ |
| 201 | addToBlockedList(db); |
| 202 | } |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 203 | db->pBlockingConnection = pBlocker; |
| 204 | leaveMutex(); |
| 205 | } |
| 206 | |
| 207 | /* |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 208 | ** This function is called when |
| 209 | ** the transaction opened by database db has just finished. Locks held |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 */ |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 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 */ |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 233 | |
drh | 65a73ba | 2009-04-07 22:06:57 +0000 | [diff] [blame] | 234 | aArg = aStatic; |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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) ); |
drh | 7c01f1d | 2009-03-25 16:51:43 +0000 | [diff] [blame] | 256 | assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn ); |
| 257 | if( (!aDyn && nArg==(int)ArraySize(aStatic)) |
drh | b975598 | 2010-07-24 16:34:37 +0000 | [diff] [blame] | 258 | || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*))) |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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. */ |
drh | 335c0fa | 2010-04-17 16:10:19 +0000 | [diff] [blame] | 305 | if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){ |
danielk1977 | 404ca07 | 2009-03-16 13:19:36 +0000 | [diff] [blame] | 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 |