Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View 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();
}
}
}

View 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;
}
}

View 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();
}

View 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;
}
}

View 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';
}
}