lock.inc

  1. drupal
    1. 6 includes/lock.inc
    2. 7 includes/lock.inc
    3. 8 core/includes/lock.inc

A database-mediated implementation of a locking mechanism.

Functions & methods

NameDescription
lock_acquireAcquire (or renew) a lock, but do not block if it fails.
lock_initializeInitialize the locking system.
lock_may_be_availableCheck if lock acquired by a different process may be available.
lock_releaseRelease a lock previously acquired by lock_acquire().
lock_release_allRelease all previously acquired locks.
lock_waitWait for a lock to be available.
_lock_idHelper function to get this request's unique id.

File

includes/lock.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * A database-mediated implementation of a locking mechanism.
  5. */
  6. /**
  7. * @defgroup lock Locking mechanisms
  8. * @{
  9. * 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 operation 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. * Initialize the locking system.
  63. */
  64. function lock_initialize() {
  65. global $locks;
  66. $locks = array();
  67. }
  68. /**
  69. * Helper function to get this request's unique id.
  70. */
  71. function _lock_id() {
  72. // Do not use drupal_static(). This identifier refers to the current
  73. // client request, and must not be changed under any circumstances
  74. // else the shutdown handler may fail to release our locks.
  75. static $lock_id;
  76. if (!isset($lock_id)) {
  77. // Assign a unique id.
  78. $lock_id = uniqid(mt_rand(), TRUE);
  79. // We only register a shutdown function if a lock is used.
  80. drupal_register_shutdown_function('lock_release_all', $lock_id);
  81. }
  82. return $lock_id;
  83. }
  84. /**
  85. * Acquire (or renew) a lock, but do not block if it fails.
  86. *
  87. * @param $name
  88. * The name of the lock.
  89. * @param $timeout
  90. * A number of seconds (float) before the lock expires (minimum of 0.001).
  91. *
  92. * @return
  93. * TRUE if the lock was acquired, FALSE if it failed.
  94. */
  95. function lock_acquire($name, $timeout = 30.0) {
  96. global $locks;
  97. // Insure that the timeout is at least 1 ms.
  98. $timeout = max($timeout, 0.001);
  99. $expire = microtime(TRUE) + $timeout;
  100. if (isset($locks[$name])) {
  101. // Try to extend the expiration of a lock we already acquired.
  102. $success = (bool) db_update('semaphore')
  103. ->fields(array('expire' => $expire))
  104. ->condition('name', $name)
  105. ->condition('value', _lock_id())
  106. ->execute();
  107. if (!$success) {
  108. // The lock was broken.
  109. unset($locks[$name]);
  110. }
  111. return $success;
  112. }
  113. else {
  114. // Optimistically try to acquire the lock, then retry once if it fails.
  115. // The first time through the loop cannot be a retry.
  116. $retry = FALSE;
  117. // We always want to do this code at least once.
  118. do {
  119. try {
  120. db_insert('semaphore')
  121. ->fields(array(
  122. 'name' => $name,
  123. 'value' => _lock_id(),
  124. 'expire' => $expire,
  125. ))
  126. ->execute();
  127. // We track all acquired locks in the global variable.
  128. $locks[$name] = TRUE;
  129. // We never need to try again.
  130. $retry = FALSE;
  131. }
  132. catch (PDOException $e) {
  133. // Suppress the error. If this is our first pass through the loop,
  134. // then $retry is FALSE. In this case, the insert must have failed
  135. // meaning some other request acquired the lock but did not release it.
  136. // We decide whether to retry by checking lock_may_be_available()
  137. // Since this will break the lock in case it is expired.
  138. $retry = $retry ? FALSE : lock_may_be_available($name);
  139. }
  140. // We only retry in case the first attempt failed, but we then broke
  141. // an expired lock.
  142. } while ($retry);
  143. }
  144. return isset($locks[$name]);
  145. }
  146. /**
  147. * Check if lock acquired by a different process may be available.
  148. *
  149. * If an existing lock has expired, it is removed.
  150. *
  151. * @param $name
  152. * The name of the lock.
  153. *
  154. * @return
  155. * TRUE if there is no lock or it was removed, FALSE otherwise.
  156. */
  157. function lock_may_be_available($name) {
  158. $lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
  159. if (!$lock) {
  160. return TRUE;
  161. }
  162. $expire = (float) $lock['expire'];
  163. $now = microtime(TRUE);
  164. if ($now > $expire) {
  165. // We check two conditions to prevent a race condition where another
  166. // request acquired the lock and set a new expire time. We add a small
  167. // number to $expire to avoid errors with float to string conversion.
  168. return (bool) db_delete('semaphore')
  169. ->condition('name', $name)
  170. ->condition('value', $lock['value'])
  171. ->condition('expire', 0.0001 + $expire, '<=')
  172. ->execute();
  173. }
  174. return FALSE;
  175. }
  176. /**
  177. * Wait for a lock to be available.
  178. *
  179. * This function may be called in a request that fails to acquire a desired
  180. * lock. This will block further execution until the lock is available or the
  181. * specified delay in seconds is reached. This should not be used with locks
  182. * that are acquired very frequently, since the lock is likely to be acquired
  183. * again by a different request while waiting.
  184. *
  185. * @param $name
  186. * The name of the lock.
  187. * @param $delay
  188. * The maximum number of seconds to wait, as an integer.
  189. *
  190. * @return
  191. * TRUE if the lock holds, FALSE if it is available.
  192. */
  193. function lock_wait($name, $delay = 30) {
  194. // Pause the process for short periods between calling
  195. // lock_may_be_available(). This prevents hitting the database with constant
  196. // database queries while waiting, which could lead to performance issues.
  197. // However, if the wait period is too long, there is the potential for a
  198. // large number of processes to be blocked waiting for a lock, especially
  199. // if the item being rebuilt is commonly requested. To address both of these
  200. // concerns, begin waiting for 25ms, then add 25ms to the wait period each
  201. // time until it reaches 500ms. After this point polling will continue every
  202. // 500ms until $delay is reached.
  203. // $delay is passed in seconds, but we will be using usleep(), which takes
  204. // microseconds as a parameter. Multiply it by 1 million so that all
  205. // further numbers are equivalent.
  206. $delay = (int) $delay * 1000000;
  207. // Begin sleeping at 25ms.
  208. $sleep = 25000;
  209. while ($delay > 0) {
  210. // This function should only be called by a request that failed to get a
  211. // lock, so we sleep first to give the parallel request a chance to finish
  212. // and release the lock.
  213. usleep($sleep);
  214. // After each sleep, increase the value of $sleep until it reaches
  215. // 500ms, to reduce the potential for a lock stampede.
  216. $delay = $delay - $sleep;
  217. $sleep = min(500000, $sleep + 25000, $delay);
  218. if (lock_may_be_available($name)) {
  219. // No longer need to wait.
  220. return FALSE;
  221. }
  222. }
  223. // The caller must still wait longer to get the lock.
  224. return TRUE;
  225. }
  226. /**
  227. * Release a lock previously acquired by lock_acquire().
  228. *
  229. * This will release the named lock if it is still held by the current request.
  230. *
  231. * @param $name
  232. * The name of the lock.
  233. */
  234. function lock_release($name) {
  235. global $locks;
  236. unset($locks[$name]);
  237. db_delete('semaphore')
  238. ->condition('name', $name)
  239. ->condition('value', _lock_id())
  240. ->execute();
  241. }
  242. /**
  243. * Release all previously acquired locks.
  244. */
  245. function lock_release_all($lock_id = NULL) {
  246. global $locks;
  247. $locks = array();
  248. if (empty($lock_id)) {
  249. $lock_id = _lock_id();
  250. }
  251. db_delete('semaphore')
  252. ->condition('value', $lock_id)
  253. ->execute();
  254. }
  255. /**
  256. * @} End of "defgroup lock".
  257. */

Comments

Uncaught exception thrown in shutdown function.

Please help to solve this issue.

PDOException: SQLSTATE[HY000]: General error: 1033 Incorrect information in file: './concertt_drpl_7_9/semaphore.frm': DELETE FROM {semaphore} WHERE (value = :db_condition_placeholder_0) ; Array ( [:db_condition_placeholder_0] => 20999261904ed48994009029.44705614 ) in lock_release_all() (line 269 of /home/concertt/public_html/includes/lock.inc).

PDOException: SQLSTATE[HY000]: General error: 1033 Incorrect information in file: './concertt_drpl_7_9/semaphore.frm': SELECT expire, value FROM {semaphore} WHERE name = :name; Array ( [:name] => variable_init ) in lock_may_be_available() (line 167 of /home/concertt/public_html/includes/lock.inc).

Thanks in advance

having same error

having same error

having similar error

Website unavailable because of

PDOException: SQLSTATE[HY000]: General error: 1033 Incorrect information in file: './charl145_pixie7/frsemaphore.frm': SELECT expire, value FROM {semaphore} WHERE name = :name; Array ( [:name] => variable_init ) in lock_may_be_available() (line 167 of /home/charl145/public_html/onewomanbiz-7.10/includes/lock.inc).

Any ideas? Site was working fine yesterday...

Please post problems to the Drupal issue queue

Please post problems to the Drupal issue queue and not the API documentation.

Thanks,
Chris

Solution to your issue

I had the same issue and found a solution. Check the following URLs:

-) https://network.acquia.com/node/1718294
-) http://dba.stackexchange.com/questions/13259/sql-command-to-remove-prefi...

Login or register to post comments