| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @file 5 * A database-mediated implementation of a locking mechanism. 6 */ 7 8 /** 9 * @defgroup lock Functions to coordinate long-running operations across requests. 10 * @{ 11 * In most environments, multiple Drupal page requests (a.k.a. threads or 12 * processes) will execute in parallel. This leads to potential conflicts or 13 * race conditions when two requests execute the same code at the same time. A 14 * common example of this is a rebuild like menu_rebuild() where we invoke many 15 * hook implementations to get and process data from all active modules, and 16 * then delete the current data in the database to insert the new afterwards. 17 * 18 * This is a cooperative, advisory lock system. Any long-running operation 19 * that could potentially be attempted in parallel by multiple requests should 20 * try to acquire a lock before proceeding. By obtaining a lock, one request 21 * notifies any other requests that a specific opertation is in progress which 22 * must not be executed in parallel. 23 * 24 * To use this API, pick a unique name for the lock. A sensible choice is the 25 * name of the function performing the operation. A very simple example use of 26 * this API: 27 * @code 28 * function mymodule_long_operation() { 29 * if (lock_acquire('mymodule_long_operation')) { 30 * // Do the long operation here. 31 * // ... 32 * lock_release('mymodule_long_operation'); 33 * } 34 * } 35 * @endcode 36 * 37 * If a function acquires a lock it should always release it when the 38 * operation is complete by calling lock_release(), as in the example. 39 * 40 * A function that has acquired a lock may attempt to renew a lock (extend the 41 * duration of the lock) by calling lock_acquire() again during the operation. 42 * Failure to renew a lock is indicative that another request has acquired 43 * the lock, and that the current operation may need to be aborted. 44 * 45 * If a function fails to acquire a lock it may either immediately return, or 46 * it may call lock_wait() if the rest of the current page request requires 47 * that the operation in question be complete. After lock_wait() returns, 48 * the function may again attempt to acquire the lock, or may simply allow the 49 * page request to proceed on the assumption that a parallel request completed 50 * the operation. 51 * 52 * lock_acquire() and lock_wait() will automatically break (delete) a lock 53 * whose duration has exceeded the timeout specified when it was acquired. 54 * 55 * Alternative implementations of this API (such as APC) may be substituted 56 * by setting the 'lock_inc' variable to an alternate include filepath. Since 57 * this is an API intended to support alternative implementations, code using 58 * this API should never rely upon specific implementation details (for example 59 * no code should look for or directly modify a lock in the {semaphore} table). 60 */ 61 62 /** 63 * Initialize the locking system. 64 */ 65 function lock_init() { 66 global $locks; 67 68 $locks = array(); 69 } 70 71 /** 72 * Helper function to get this request's unique id. 73 */ 74 function _lock_id() { 75 static $lock_id; 76 77 if (!isset($lock_id)) { 78 // Assign a unique id. 79 $lock_id = uniqid(mt_rand(), TRUE); 80 // We only register a shutdown function if a lock is used. 81 register_shutdown_function('lock_release_all', $lock_id); 82 } 83 return $lock_id; 84 } 85 86 /** 87 * Acquire (or renew) a lock, but do not block if it fails. 88 * 89 * @param $name 90 * The name of the lock. 91 * @param $timeout 92 * A number of seconds (float) before the lock expires. 93 * @return 94 * TRUE if the lock was acquired, FALSE if it failed. 95 */ 96 function lock_acquire($name, $timeout = 30.0) { 97 global $locks; 98 99 // Insure that the timeout is at least 1 ms. 100 $timeout = max($timeout, 0.001); 101 list($usec, $sec) = explode(' ', microtime()); 102 $expire = (float)$usec + (float)$sec + $timeout; 103 if (isset($locks[$name])) { 104 // Try to extend the expiration of a lock we already acquired. 105 db_query("UPDATE {semaphore} SET expire = %f WHERE name = '%s' AND value = '%s'", $expire, $name, _lock_id()); 106 if (!db_affected_rows()) { 107 // The lock was broken. 108 unset($locks[$name]); 109 } 110 } 111 else { 112 // Optimistically try to acquire the lock, then retry once if it fails. 113 // The first time through the loop cannot be a retry. 114 $retry = FALSE; 115 // We always want to do this code at least once. 116 do { 117 if (@db_query("INSERT INTO {semaphore} (name, value, expire) VALUES ('%s', '%s', %f)", $name, _lock_id(), $expire)) { 118 // We track all acquired locks in the global variable. 119 $locks[$name] = TRUE; 120 // We never need to try again. 121 $retry = FALSE; 122 } 123 else { 124 // Suppress the error. If this is our first pass through the loop, 125 // then $retry is FALSE. In this case, the insert must have failed 126 // meaning some other request acquired the lock but did not release it. 127 // We decide whether to retry by checking lock_may_be_available() 128 // Since this will break the lock in case it is expired. 129 $retry = $retry ? FALSE : lock_may_be_available($name); 130 } 131 // We only retry in case the first attempt failed, but we then broke 132 // an expired lock. 133 } while ($retry); 134 } 135 return isset($locks[$name]); 136 } 137 138 /** 139 * Check if lock acquired by a different process may be available. 140 * 141 * If an existing lock has expired, it is removed. 142 * 143 * @param $name 144 * The name of the lock. 145 * @return 146 * TRUE if there is no lock or it was removed, FALSE otherwise. 147 */ 148 function lock_may_be_available($name) { 149 $lock = db_fetch_array(db_query("SELECT expire, value FROM {semaphore} WHERE name = '%s'", $name)); 150 if (!$lock) { 151 return TRUE; 152 } 153 $expire = (float) $lock['expire']; 154 list($usec, $sec) = explode(' ', microtime()); 155 $now = (float)$usec + (float)$sec; 156 if ($now > $lock['expire']) { 157 // We check two conditions to prevent a race condition where another 158 // request acquired the lock and set a new expire time. We add a small 159 // number to $expire to avoid errors with float to string conversion. 160 db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s' AND expire <= %f", $name, $lock['value'], 0.0001 + $expire); 161 return (bool)db_affected_rows(); 162 } 163 return FALSE; 164 } 165 166 /** 167 * Wait for a lock to be available. 168 * 169 * This function may be called in a request that fails to acquire a desired 170 * lock. This will block further execution until the lock is available or the 171 * specified delay in seconds is reached. This should not be used with locks 172 * that are acquired very frequently, since the lock is likely to be acquired 173 * again by a different request during the sleep(). 174 * 175 * @param $name 176 * The name of the lock. 177 * @param $delay 178 * The maximum number of seconds to wait, as an integer. 179 * @return 180 * TRUE if the lock holds, FALSE if it is available. 181 */ 182 function lock_wait($name, $delay = 30) { 183 184 while ($delay--) { 185 // This function should only be called by a request that failed to get a 186 // lock, so we sleep first to give the parallel request a chance to finish 187 // and release the lock. 188 sleep(1); 189 if (lock_may_be_available($name)) { 190 // No longer need to wait. 191 return FALSE; 192 } 193 } 194 // The caller must still wait longer to get the lock. 195 return TRUE; 196 } 197 198 /** 199 * Release a lock previously acquired by lock_acquire(). 200 * 201 * This will release the named lock if it is still held by the current request. 202 * 203 * @param $name 204 * The name of the lock. 205 */ 206 function lock_release($name) { 207 global $locks; 208 209 unset($locks[$name]); 210 db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s'", $name, _lock_id()); 211 } 212 213 /** 214 * Release all previously acquired locks. 215 */ 216 function lock_release_all($lock_id = NULL) { 217 global $locks; 218 219 $locks = array(); 220 if (empty($lock_id)) { 221 $lock_id = _lock_id(); 222 } 223 224 db_query("DELETE FROM {semaphore} WHERE value = '%s'", _lock_id()); 225 } 226 227 /** 228 * @} End of "defgroup locks". 229 */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |