| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: lock.inc,v 1.1.2.4 2010/05/11 09:30:20 goba Exp $ 3 4 /** 5 * @file 6 * A database-mediated implementation of a locking mechanism. 7 */ 8 9 /** 10 * @defgroup lock Functions to coordinate long-running operations across requests. 11 * @{ 12 * In most environments, multiple Drupal page requests (a.k.a. threads or 13 * processes) will execute in parallel. This leads to potential conflicts or 14 * race conditions when two requests execute the same code at the same time. A 15 * common example of this is a rebuild like menu_rebuild() where we invoke many 16 * hook implementations to get and process data from all active modules, and 17 * then delete the current data in the database to insert the new afterwards. 18 * 19 * This is a cooperative, advisory lock system. Any long-running operation 20 * that could potentially be attempted in parallel by multiple requests should 21 * try to acquire a lock before proceeding. By obtaining a lock, one request 22 * notifies any other requests that a specific opertation is in progress which 23 * must not be executed in parallel. 24 * 25 * To use this API, pick a unique name for the lock. A sensible choice is the 26 * name of the function performing the operation. A very simple example use of 27 * this API: 28 * @code 29 * function mymodule_long_operation() { 30 * if (lock_acquire('mymodule_long_operation')) { 31 * // Do the long operation here. 32 * // ... 33 * lock_release('mymodule_long_operation'); 34 * } 35 * } 36 * @endcode 37 * 38 * If a function acquires a lock it should always release it when the 39 * operation is complete by calling lock_release(), as in the example. 40 * 41 * A function that has acquired a lock may attempt to renew a lock (extend the 42 * duration of the lock) by calling lock_acquire() again during the operation. 43 * Failure to renew a lock is indicative that another request has acquired 44 * the lock, and that the current operation may need to be aborted. 45 * 46 * If a function fails to acquire a lock it may either immediately return, or 47 * it may call lock_wait() if the rest of the current page request requires 48 * that the operation in question be complete. After lock_wait() returns, 49 * the function may again attempt to acquire the lock, or may simply allow the 50 * page request to proceed on the assumption that a parallel request completed 51 * the operation. 52 * 53 * lock_acquire() and lock_wait() will automatically break (delete) a lock 54 * whose duration has exceeded the timeout specified when it was acquired. 55 * 56 * Alternative implementations of this API (such as APC) may be substituted 57 * by setting the 'lock_inc' variable to an alternate include filepath. Since 58 * this is an API intended to support alternative implementations, code using 59 * this API should never rely upon specific implementation details (for example 60 * no code should look for or directly modify a lock in the {semaphore} table). 61 */ 62 63 /** 64 * Initialize the locking system. 65 */ 66 function lock_init() { 67 global $locks; 68 69 $locks = array(); 70 } 71 72 /** 73 * Helper function to get this request's unique id. 74 */ 75 function _lock_id() { 76 static $lock_id; 77 78 if (!isset($lock_id)) { 79 // Assign a unique id. 80 $lock_id = uniqid(mt_rand(), TRUE); 81 // We only register a shutdown function if a lock is used. 82 register_shutdown_function('lock_release_all', $lock_id); 83 } 84 return $lock_id; 85 } 86 87 /** 88 * Acquire (or renew) a lock, but do not block if it fails. 89 * 90 * @param $name 91 * The name of the lock. 92 * @param $timeout 93 * A number of seconds (float) before the lock expires. 94 * @return 95 * TRUE if the lock was acquired, FALSE if it failed. 96 */ 97 function lock_acquire($name, $timeout = 30.0) { 98 global $locks; 99 100 // Insure that the timeout is at least 1 ms. 101 $timeout = max($timeout, 0.001); 102 list($usec, $sec) = explode(' ', microtime()); 103 $expire = (float)$usec + (float)$sec + $timeout; 104 if (isset($locks[$name])) { 105 // Try to extend the expiration of a lock we already acquired. 106 if (!db_result(db_query("UPDATE {semaphore} SET expire = %f WHERE name = '%s' AND value = '%s'", $expire, $name, _lock_id()))) { 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: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |