diff --git a/dmemcache.inc b/dmemcache.inc index d671e594aeed39bf584e02f383736e8a43bf4da7..5b0629a4a59aadac55ac1e6d517b2b09326c8dbb 100644 --- a/dmemcache.inc +++ b/dmemcache.inc @@ -93,6 +93,7 @@ function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag */ function dmemcache_get($key, $bin = 'cache', $mc = NULL) { global $_memcache_statistics; + $result = FALSE; $full_key = dmemcache_key($key, $bin); $statistics = array('get', $bin, $full_key); $success = '0'; @@ -276,7 +277,7 @@ function dmemcache_stats($stats_bin = 'cache', $stats_type = 'default', $aggrega * @return an Memcache object or FALSE. */ function dmemcache_object($bin = NULL, $flush = FALSE) { - static $extension, $memcacheCache = array(), $memcache_servers, $memcache_bins, $memcache_persistent; + static $extension, $memcacheCache = array(), $memcache_servers, $memcache_bins, $memcache_persistent, $failed_connection_cache; if (!isset($extension)) { // If an extension is specified in settings.php, use that when available. @@ -361,7 +362,7 @@ function dmemcache_object($bin = NULL, $flush = FALSE) { // Link all the servers to this cluster. foreach ($memcache_servers as $s => $c) { - if ($c == $cluster) { + if ($c == $cluster && !isset($failed_connection_cache[$s])) { list($host, $port) = explode(':', $s); // Support unix sockets in the format 'unix:///path/to/socket'. @@ -400,6 +401,20 @@ function dmemcache_object($bin = NULL, $flush = FALSE) { else if ($memcache->addServer($host, $port) && !$init) { $init = TRUE; } + + if (!$init) { + // Ensure we use an available t() function. + $t = get_t(); + $error_msg = $t( + 'Failed to connect to memcache server: %server', + array('%server' => $s) + ); + // We can't use watchdog because this happens in a bootstrap phase + // where watchdog is non-functional. Thus use trigger_error() to + // start drupal_error_handler(). + trigger_error($error_msg, E_USER_ERROR); + $failed_connection_cache[$s] = FALSE; + } } } diff --git a/memcache-lock-code.inc b/memcache-lock-code.inc new file mode 100644 index 0000000000000000000000000000000000000000..374d02b0374bb04a27f88b260454d13a845dcb40 --- /dev/null +++ b/memcache-lock-code.inc @@ -0,0 +1,161 @@ + 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 (!dmemcache_get($name, 'semaphore')) { + // No longer need to wait. + return FALSE; + } + } + // The caller must still wait longer to get the lock. + return TRUE; +} + +/** + * Release a lock previously acquired by lock_acquire(). + * + * This will release the named lock if it is still held by the current request. + * + * @param $name + * The name of the lock. + */ +function lock_release($name) { + global $locks; + + dmemcache_delete($name, 'semaphore'); + // We unset unconditionally since caller assumes lock is released anyway. + unset($locks[$name]); +} + +/** + * Generate a unique identifier for locks generated during this request. + */ +function _lock_id() { + static $lock_id; + if (!isset($lock_id)) { + $lock_id = uniqid(mt_rand(), TRUE); + // We only register a shutdown function if a lock is used. + register_shutdown_function('lock_release_all', $lock_id); + } + return $lock_id; +} + +/** + * Release all locks acquired by this request. + */ +function lock_release_all($lock_id) { + global $locks; + foreach ($locks as $name => $id) { + $value = dmemcache_get($name, 'semaphore'); + + if ($value == $id) { + dmemcache_delete($name, 'semaphore'); + } + } +} diff --git a/memcache-lock.inc b/memcache-lock.inc index 0d78cb4b2e6f247730a360778bd6ff586f623489..bc0f936843c995c4e971b3c21ec1d05757c7c31a 100644 --- a/memcache-lock.inc +++ b/memcache-lock.inc @@ -8,153 +8,11 @@ require_once dirname(__FILE__) . '/dmemcache.inc'; -/** - * Initialize the locking system. - */ -function lock_initialize() { - global $locks; - - $locks = array(); -} - -/** - * Acquire (or renew) a lock, but do not block if it fails. - * - * @param $name - * The name of the lock. - * @param $timeout - * A number of seconds (int) before the lock expires (minimum of 1). - * @return - * TRUE if the lock was acquired, FALSE if it failed. - */ -function lock_acquire($name, $timeout = 30) { - global $locks; - - // Ensure that the timeout is at least 1 sec. This is a limitation - // imposed by memcached. - $timeout = (int) max($timeout, 1); - - if (dmemcache_add($name, _lock_id(), $timeout, 'semaphore')) { - $locks[$name] = _lock_id(); - } - elseif ($result = dmemcache_get($name, 'semaphore') && isset($locks[$name]) && $locks[$name] == _lock_id()) { - // Only renew the lock if we already set it and it has not expired. - dmemcache_set($name, _lock_id(), $timeout, 'semaphore'); - } - else { - // Failed to acquire the lock. Unset the key from the $locks array even if - // not set, PHP 5+ allows this without error or warning. - unset($locks[$name]); - } - - return isset($locks[$name]); -} - -/** - * Check if lock acquired by a different process may be available. - * - * If an existing lock has expired, it is removed. - * - * @param $name - * The name of the lock. - * @return - * TRUE if there is no lock or it was removed, FALSE otherwise. - */ -function lock_may_be_available($name) { - return !dmemcache_get($name, 'semaphore'); -} - -/** - * Wait for a lock to be available. - * - * This function may be called in a request that fails to acquire a desired - * lock. This will block further execution until the lock is available or the - * specified delay in seconds is reached. This should not be used with locks - * that are acquired very frequently, since the lock is likely to be acquired - * again by a different request while waiting. - * - * @param $name - * The name of the lock. - * @param $delay - * The maximum number of seconds to wait, as an integer. - * @return - * TRUE if the lock holds, FALSE if it is available. - */ -function lock_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 (!dmemcache_get($name, 'semaphore')) { - // No longer need to wait. - return FALSE; - } - } - // The caller must still wait longer to get the lock. - return TRUE; -} - -/** - * Release a lock previously acquired by lock_acquire(). - * - * This will release the named lock if it is still held by the current request. - * - * @param $name - * The name of the lock. - */ -function lock_release($name) { - global $locks; - - dmemcache_delete($name, 'semaphore'); - // We unset unconditionally since caller assumes lock is released anyway. - unset($locks[$name]); -} - -/** - * Generate a unique identifier for locks generated during this request. - */ -function _lock_id() { - static $lock_id; - if (!isset($lock_id)) { - $lock_id = uniqid(mt_rand(), TRUE); - // We only register a shutdown function if a lock is used. - register_shutdown_function('lock_release_all', $lock_id); - } - return $lock_id; -} - -/** - * Release all locks acquired by this request. - */ -function lock_release_all($lock_id) { - global $locks; - foreach ($locks as $name => $id) { - $value = dmemcache_get($name, 'semaphore'); - - if ($value == $id) { - dmemcache_delete($name, 'semaphore'); - } - } +// Check if memcached is available - if not include default lock handler. +// @todo get rid of this conditional include as soon as this is done: +// http://drupal.org/node/1225404 +$lock_file = dirname(__FILE__) . '/memcache-lock-code.inc'; +if (!dmemcache_object('semaphore')) { + $lock_file = DRUPAL_ROOT . '/includes/lock.inc'; } +require_once $lock_file; \ No newline at end of file diff --git a/memcache.inc b/memcache.inc index f16c442c7284aefecdfa909b53bde993c9c3c0e7..547d9c46b5e9f730e83feb8be5508ad42406634c 100644 --- a/memcache.inc +++ b/memcache.inc @@ -146,6 +146,10 @@ class MemCacheDrupal implements DrupalCacheInterface { } function clear($cid = NULL, $wildcard = FALSE) { + if ($this->memcache === FALSE) { + // No memcache connection. + return; + } // It is not possible to detect a cache_clear_all() call other than looking // at the backtrace unless http://drupal.org/node/81461 is added.