Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
143
core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
Normal file
143
core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Lock\DatabaseLockBackend.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Lock;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\IntegrityConstraintViolationException;
|
||||
|
||||
/**
|
||||
* Defines the database lock backend. This is the default backend in Drupal.
|
||||
*
|
||||
* @ingroup lock
|
||||
*/
|
||||
class DatabaseLockBackend extends LockBackendAbstract {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Constructs a new DatabaseLockBackend.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(Connection $database) {
|
||||
// __destruct() is causing problems with garbage collections, register a
|
||||
// shutdown function instead.
|
||||
drupal_register_shutdown_function(array($this, 'releaseAll'));
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::acquire().
|
||||
*/
|
||||
public function acquire($name, $timeout = 30.0) {
|
||||
// Insure that the timeout is at least 1 ms.
|
||||
$timeout = max($timeout, 0.001);
|
||||
$expire = microtime(TRUE) + $timeout;
|
||||
if (isset($this->locks[$name])) {
|
||||
// Try to extend the expiration of a lock we already acquired.
|
||||
$success = (bool) $this->database->update('semaphore')
|
||||
->fields(array('expire' => $expire))
|
||||
->condition('name', $name)
|
||||
->condition('value', $this->getLockId())
|
||||
->execute();
|
||||
if (!$success) {
|
||||
// The lock was broken.
|
||||
unset($this->locks[$name]);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
else {
|
||||
// Optimistically try to acquire the lock, then retry once if it fails.
|
||||
// The first time through the loop cannot be a retry.
|
||||
$retry = FALSE;
|
||||
// We always want to do this code at least once.
|
||||
do {
|
||||
try {
|
||||
$this->database->insert('semaphore')
|
||||
->fields(array(
|
||||
'name' => $name,
|
||||
'value' => $this->getLockId(),
|
||||
'expire' => $expire,
|
||||
))
|
||||
->execute();
|
||||
// We track all acquired locks in the global variable.
|
||||
$this->locks[$name] = TRUE;
|
||||
// We never need to try again.
|
||||
$retry = FALSE;
|
||||
}
|
||||
catch (IntegrityConstraintViolationException $e) {
|
||||
// Suppress the error. If this is our first pass through the loop,
|
||||
// then $retry is FALSE. In this case, the insert failed because some
|
||||
// other request acquired the lock but did not release it. We decide
|
||||
// whether to retry by checking lockMayBeAvailable(). This will clear
|
||||
// the offending row from the database table in case it has expired.
|
||||
$retry = $retry ? FALSE : $this->lockMayBeAvailable($name);
|
||||
}
|
||||
// We only retry in case the first attempt failed, but we then broke
|
||||
// an expired lock.
|
||||
} while ($retry);
|
||||
}
|
||||
return isset($this->locks[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::lockMayBeAvailable().
|
||||
*/
|
||||
public function lockMayBeAvailable($name) {
|
||||
$lock = $this->database->query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
|
||||
if (!$lock) {
|
||||
return TRUE;
|
||||
}
|
||||
$expire = (float) $lock['expire'];
|
||||
$now = microtime(TRUE);
|
||||
if ($now > $expire) {
|
||||
// We check two conditions to prevent a race condition where another
|
||||
// request acquired the lock and set a new expire time. We add a small
|
||||
// number to $expire to avoid errors with float to string conversion.
|
||||
return (bool) $this->database->delete('semaphore')
|
||||
->condition('name', $name)
|
||||
->condition('value', $lock['value'])
|
||||
->condition('expire', 0.0001 + $expire, '<=')
|
||||
->execute();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::release().
|
||||
*/
|
||||
public function release($name) {
|
||||
unset($this->locks[$name]);
|
||||
$this->database->delete('semaphore')
|
||||
->condition('name', $name)
|
||||
->condition('value', $this->getLockId())
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::releaseAll().
|
||||
*/
|
||||
public function releaseAll($lock_id = NULL) {
|
||||
// Only attempt to release locks if any were acquired.
|
||||
if (!empty($this->locks)) {
|
||||
$this->locks = array();
|
||||
if (empty($lock_id)) {
|
||||
$lock_id = $this->getLockId();
|
||||
}
|
||||
$this->database->delete('semaphore')
|
||||
->condition('value', $lock_id)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
79
core/lib/Drupal/Core/Lock/LockBackendAbstract.php
Normal file
79
core/lib/Drupal/Core/Lock/LockBackendAbstract.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Lock\LockBackendAbstract.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Lock;
|
||||
|
||||
/**
|
||||
* Non backend related common methods implementation for lock backends.
|
||||
*
|
||||
* @ingroup lock
|
||||
*/
|
||||
abstract class LockBackendAbstract implements LockBackendInterface {
|
||||
|
||||
/**
|
||||
* Current page lock token identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $lockId;
|
||||
|
||||
/**
|
||||
* Existing locks for this page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $locks = array();
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::wait().
|
||||
*/
|
||||
public function wait($name, $delay = 30) {
|
||||
// Pause the process for short periods between calling
|
||||
// lock_may_be_available(). This prevents hitting the database with constant
|
||||
// database queries while waiting, which could lead to performance issues.
|
||||
// However, if the wait period is too long, there is the potential for a
|
||||
// large number of processes to be blocked waiting for a lock, especially
|
||||
// if the item being rebuilt is commonly requested. To address both of these
|
||||
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
|
||||
// time until it reaches 500ms. After this point polling will continue every
|
||||
// 500ms until $delay is reached.
|
||||
|
||||
// $delay is passed in seconds, but we will be using usleep(), which takes
|
||||
// microseconds as a parameter. Multiply it by 1 million so that all
|
||||
// further numbers are equivalent.
|
||||
$delay = (int) $delay * 1000000;
|
||||
|
||||
// Begin sleeping at 25ms.
|
||||
$sleep = 25000;
|
||||
while ($delay > 0) {
|
||||
// This function should only be called by a request that failed to get a
|
||||
// lock, so we sleep first to give the parallel request a chance to finish
|
||||
// and release the lock.
|
||||
usleep($sleep);
|
||||
// After each sleep, increase the value of $sleep until it reaches
|
||||
// 500ms, to reduce the potential for a lock stampede.
|
||||
$delay = $delay - $sleep;
|
||||
$sleep = min(500000, $sleep + 25000, $delay);
|
||||
if ($this->lockMayBeAvailable($name)) {
|
||||
// No longer need to wait.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// The caller must still wait longer to get the lock.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::getLockId().
|
||||
*/
|
||||
public function getLockId() {
|
||||
if (!isset($this->lockId)) {
|
||||
$this->lockId = uniqid(mt_rand(), TRUE);
|
||||
}
|
||||
return $this->lockId;
|
||||
}
|
||||
}
|
136
core/lib/Drupal/Core/Lock/LockBackendInterface.php
Normal file
136
core/lib/Drupal/Core/Lock/LockBackendInterface.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Lock\LockBackendInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Lock;
|
||||
|
||||
/**
|
||||
* @defgroup lock Locking mechanisms
|
||||
* @{
|
||||
* Functions to coordinate long-running operations across requests.
|
||||
*
|
||||
* In most environments, multiple Drupal page requests (a.k.a. threads or
|
||||
* processes) will execute in parallel. This leads to potential conflicts or
|
||||
* race conditions when two requests execute the same code at the same time. A
|
||||
* common example of this is a rebuild like menu_router_rebuild() where we
|
||||
* invoke many hook implementations to get and process data from all active
|
||||
* modules, and then delete the current data in the database to insert the new
|
||||
* afterwards.
|
||||
*
|
||||
* This is a cooperative, advisory lock system. Any long-running operation
|
||||
* that could potentially be attempted in parallel by multiple requests should
|
||||
* try to acquire a lock before proceeding. By obtaining a lock, one request
|
||||
* notifies any other requests that a specific operation is in progress which
|
||||
* must not be executed in parallel.
|
||||
*
|
||||
* To use this API, pick a unique name for the lock. A sensible choice is the
|
||||
* name of the function performing the operation. A very simple example use of
|
||||
* this API:
|
||||
* @code
|
||||
* function mymodule_long_operation() {
|
||||
* $lock = \Drupal::lock();
|
||||
* if ($lock->acquire('mymodule_long_operation')) {
|
||||
* // Do the long operation here.
|
||||
* // ...
|
||||
* $lock->release('mymodule_long_operation');
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* If a function acquires a lock it should always release it when the operation
|
||||
* is complete by calling $lock->release(), as in the example.
|
||||
*
|
||||
* A function that has acquired a lock may attempt to renew a lock (extend the
|
||||
* duration of the lock) by calling $lock->acquire() again during the operation.
|
||||
* Failure to renew a lock is indicative that another request has acquired the
|
||||
* lock, and that the current operation may need to be aborted.
|
||||
*
|
||||
* If a function fails to acquire a lock it may either immediately return, or
|
||||
* it may call $lock->wait() if the rest of the current page request requires
|
||||
* that the operation in question be complete. After $lock->wait() returns, the
|
||||
* function may again attempt to acquire the lock, or may simply allow the page
|
||||
* request to proceed on the assumption that a parallel request completed the
|
||||
* operation.
|
||||
*
|
||||
* $lock->acquire() and $lock->wait() will automatically break (delete) a lock
|
||||
* whose duration has exceeded the timeout specified when it was acquired.
|
||||
*
|
||||
* @} End of "defgroup lock".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Lock backend interface.
|
||||
*
|
||||
* @ingroup lock
|
||||
*/
|
||||
interface LockBackendInterface {
|
||||
|
||||
/**
|
||||
* Acquires a lock.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name. Limit of name's length is 255 characters.
|
||||
* @param float $timeout = 30.0
|
||||
* (optional) Lock lifetime in seconds.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acquire($name, $timeout = 30.0);
|
||||
|
||||
/**
|
||||
* Checks if a lock is available for acquiring.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock to acquire.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lockMayBeAvailable($name);
|
||||
|
||||
/**
|
||||
* Waits a short amount of time before a second lock acquire attempt.
|
||||
*
|
||||
* While this method is subject to have a generic implementation in abstract
|
||||
* backend implementation, some backends may provide non blocking or less I/O
|
||||
* intensive wait mechanism: this is why this method remains on the backend
|
||||
* interface.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name currently being locked.
|
||||
* @param int $delay = 30
|
||||
* Milliseconds to wait for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the lock holds, FALSE if it may be available. You still need to
|
||||
* acquire the lock manually and it may fail again.
|
||||
*/
|
||||
public function wait($name, $delay = 30);
|
||||
|
||||
/**
|
||||
* Releases the given lock.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function release($name);
|
||||
|
||||
/**
|
||||
* Releases all locks for the given lock token identifier.
|
||||
*
|
||||
* @param string $lockId
|
||||
* (optional) If none given, remove all locks from the current page.
|
||||
* Defaults to NULL.
|
||||
*/
|
||||
public function releaseAll($lockId = NULL);
|
||||
|
||||
/**
|
||||
* Gets the unique page token for locks.
|
||||
*
|
||||
* Locks will be wiped out at the end of each page request on a token basis.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLockId();
|
||||
}
|
65
core/lib/Drupal/Core/Lock/NullLockBackend.php
Normal file
65
core/lib/Drupal/Core/Lock/NullLockBackend.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Lock\NullLockBackend.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Lock;
|
||||
|
||||
/**
|
||||
* Defines a Null lock backend.
|
||||
*
|
||||
* This implementation won't actually lock anything and will always succeed on
|
||||
* lock attempts.
|
||||
*
|
||||
* @ingroup lock
|
||||
*/
|
||||
class NullLockBackend implements LockBackendInterface {
|
||||
|
||||
/**
|
||||
* Current page lock token identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $lockId;
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::acquire().
|
||||
*/
|
||||
public function acquire($name, $timeout = 30.0) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::lockMayBeAvailable().
|
||||
*/
|
||||
public function lockMayBeAvailable($name) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::wait().
|
||||
*/
|
||||
public function wait($name, $delay = 30) {}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::release().
|
||||
*/
|
||||
public function release($name) {}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::releaseAll().
|
||||
*/
|
||||
public function releaseAll($lock_id = NULL) {}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Lock\LockBackedInterface::getLockId().
|
||||
*/
|
||||
public function getLockId() {
|
||||
if (!isset($this->lockId)) {
|
||||
$this->lockId = uniqid(mt_rand(), TRUE);
|
||||
}
|
||||
return $this->lockId;
|
||||
}
|
||||
}
|
36
core/lib/Drupal/Core/Lock/PersistentDatabaseLockBackend.php
Normal file
36
core/lib/Drupal/Core/Lock/PersistentDatabaseLockBackend.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Lock\PersistentDatabaseLockBackend.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Lock;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Defines the persistent database lock backend. This backend is global for this
|
||||
* Drupal installation.
|
||||
*
|
||||
* @ingroup lock
|
||||
*/
|
||||
class PersistentDatabaseLockBackend extends DatabaseLockBackend {
|
||||
|
||||
/**
|
||||
* Constructs a new PersistentDatabaseLockBackend.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(Connection $database) {
|
||||
// Do not call the parent constructor to avoid registering a shutdown
|
||||
// function that releases all the locks at the end of a request.
|
||||
$this->database = $database;
|
||||
// Set the lockId to a fixed string to make the lock ID the same across
|
||||
// multiple requests. The lock ID is used as a page token to relate all the
|
||||
// locks set during a request to each other.
|
||||
// @see \Drupal\Core\Lock\LockBackendInterface::getLockId()
|
||||
$this->lockId = 'persistent';
|
||||
}
|
||||
}
|
Reference in a new issue