summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Andrews2014-02-17 21:58:44 (GMT)
committerJeremy Andrews2014-02-17 21:58:44 (GMT)
commitd9e5e3588edaee3f4f5ccab27b67a31e19eae267 (patch)
treeb80aa5fafe34aade9b8baf82ad67ac3453aa69a2
parent03584eaa9fa6fee3e8f4e33d168b71ad575078c3 (diff)
Issue #1172754 by Jeremy, markpavlitski: improve error reporting when there are installation problems.7.x-1.1-beta3
-rw-r--r--dmemcache.inc293
-rw-r--r--memcache.install91
-rw-r--r--memcache.module28
3 files changed, 278 insertions, 134 deletions
diff --git a/dmemcache.inc b/dmemcache.inc
index d8021ac..129d2a2 100644
--- a/dmemcache.inc
+++ b/dmemcache.inc
@@ -278,46 +278,179 @@ function dmemcache_stats($stats_bin = 'cache', $stats_type = 'default', $aggrega
}
/**
- * Returns an Memcache object based on the bin requested. Note that there is
- * nothing preventing developers from calling this function directly to get the
- * Memcache object. Do this if you need functionality not provided by this API
- * or if you need to use legacy code. Otherwise, use the dmemcache (get, set,
- * delete, flush) API functions provided here.
+ * Determine which memcache extension to use: memcache or memcached.
*
- * @param $bin The bin which is to be used.
- *
- * @param $flush Rebuild the bin/server/cache mapping.
- *
- * @return an Memcache object or FALSE.
+ * By default prefer the 'Memcache' PHP extension, though the default can be
+ * overridden by setting memcache_extension in settings.php.
*/
-function dmemcache_object($bin = NULL, $flush = FALSE) {
- static $extension, $memcacheCache = array(), $memcache_servers, $memcache_bins, $memcache_persistent, $failed_connection_cache;
-
- if (!isset($extension)) {
+function dmemcache_extension() {
+ static $extension = NULL;
+ if ($extension === NULL) {
// If an extension is specified in settings.php, use that when available.
$preferred = variable_get('memcache_extension', NULL);
if (isset($preferred) && class_exists($preferred)) {
- $extension = $preferred;
+ $extension = ucfirst(strtolower($preferred));
}
// If no extension is set, default to Memcache.
- // The Memcached extension has some features that the older extension lacks
- // but also an unfixed bug that affects cache clears.
- // @see http://pecl.php.net/bugs/bug.php?id=16829
elseif (class_exists('Memcache')) {
$extension = 'Memcache';
}
elseif (class_exists('Memcached')) {
$extension = 'Memcached';
}
+ else {
+ $extension = FALSE;
+ }
+ }
+ return $extension;
+}
+
+/**
+ * Return a new memcache instance.
+ */
+function dmemcache_instance() {
+ static $error = FALSE;
+ $extension = dmemcache_extension();
+ if ($extension == 'Memcache') {
+ return new Memcache;
+ }
+ elseif ($extension == 'Memcached') {
+ $memcache = new Memcached;
+ $default_opts = array(
+ Memcached::OPT_COMPRESSION => FALSE,
+ Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
+ );
+ foreach ($default_opts as $key => $value) {
+ $memcache->setOption($key, $value);
+ }
+ // See README.txt for setting custom Memcache options when using the
+ // memcached PECL extension.
+ $memconf = variable_get('memcache_options', array());
+ foreach ($memconf as $key => $value) {
+ $memcache->setOption($key, $value);
+ }
+ return $memcache;
+ }
+ else {
+ if (!$error) {
+ register_shutdown_function('watchdog', 'memcache', 'You must enable the PHP <a href="http://php.net/manual/en/book.memcache.php">memcache</a> (recommended) or <a href="http://php.net/manual/en/book.memcached.php">memcached</a> extension to use memcache.inc.', array(), WATCHDOG_ERROR);
+ $error = TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Initiate a connection to memcache.
+ *
+ * @param $memcache A memcache instance obtained through dmemcache_instance.
+ *
+ * @param $server A server string of the format "localhost:11211" or
+ * "unix:///path/to/socket".
+ *
+ * @connection TRUE or FALSE, whether the $memcache instance already has at
+ * least one open connection.
+ *
+ * @return TRUE or FALSE if connection was successful.
+ */
+function dmemcache_connect($memcache, $server, $connection) {
+ static $memcache_persistent = NULL;
- // Indicate whether to connect to memcache using a persistent connection.
- // Note: this only affects the Memcache PECL extension, and does not
- // affect the Memcached PECL extension. For a detailed explanation see:
- // http://drupal.org/node/822316#comment-4427676
+ $extension = dmemcache_extension();
+
+ list($host, $port) = explode(':', $server);
+
+ if ($extension == 'Memcache') {
+ // Allow persistent connection via Memcache extension -- note that this
+ // module currently doesn't support persistent connections with the
+ // Memcached extension. See http://drupal.org/node/822316#comment-4427676
+ // for details.
if (!isset($memcache_persistent)) {
$memcache_persistent = variable_get('memcache_persistent', FALSE);
}
+
+ // Support unix sockets of the format 'unix:///path/to/socket'.
+ if ($host == 'unix') {
+ // Use full protocol and path as expected by Memcache extension.
+ $host = $server;
+ $port = 0;
+ }
+
+ // When using the PECL memcache extension, we must use ->(p)connect
+ // for the first connection.
+ if (!$connection) {
+ $track_errors = ini_set('track_errors', '1');
+ $php_errormsg = '';
+
+ // The Memcache extension requires us to use (p)connect for the first
+ // server we connect to.
+ if ($memcache_persistent) {
+ $rc = @$memcache->pconnect($host, $port);
+ }
+ else {
+ $rc = @$memcache->connect($host, $port);
+ }
+ if (!empty($php_errormsg)) {
+ register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_object: !msg', array('!msg' => $php_errormsg), WATCHDOG_WARNING);
+ $php_errormsg = '';
+ }
+ ini_set('track_errors', $track_errors);
+ }
+ else {
+ $rc = $memcache->addServer($host, $port, $memcache_persistent);
+ }
+ }
+ else if ($extension == 'Memcached') {
+ // Support unix sockets of the format 'unix:///path/to/socket'.
+ if ($host == 'unix') {
+ // Strip 'unix://' as expected by Memcached extension.
+ $host = substr($server, 7);
+ $port = 0;
+ }
+ $rc = $memcache->addServer($host, $port);
+ }
+ else {
+ $rc = FALSE;
+ }
+ return $rc;
+}
+
+/**
+ * Close the connection to the memcache instance.
+ */
+function dmemcache_close($memcache) {
+ $extension = dmemcache_extension();
+ if ($extension == 'Memcache' && $memcache instanceof Memcache) {
+ $rc = @$memcache->close;
+ }
+ else if ($extension == 'Memcached' && $memcache instanceof Memcached) {
+ $rc = @$memcache->quit;
}
+ else {
+ $rc = FALSE;
+ }
+ return $rc;
+}
+
+/**
+ * Return a Memcache object for the specified bin.
+ *
+ * Note that there is nothing preventing developers from calling this function
+ * directly to get the Memcache object. Do this if you need functionality not
+ * provided by this API or if you need to use legacy code. Otherwise, use the
+ * dmemcache (get, set, delete, flush) API functions provided here.
+ *
+ * @param $bin The bin which is to be used.
+ *
+ * @param $flush Rebuild the bin/server/cache mapping.
+ *
+ * @return a Memcache object or FALSE.
+ */
+function dmemcache_object($bin = NULL, $flush = FALSE) {
+ static $memcacheCache = array();
+ static $memcache_servers = array();
+ static $memcache_bins = array();
+ static $failed_connections = array();
if ($flush) {
foreach ($memcacheCache as $cluster) {
@@ -326,115 +459,49 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
$memcacheCache = array();
}
+ $extension = dmemcache_extension();
+
if (empty($memcacheCache) || empty($memcacheCache[$bin])) {
- // $memcache_servers and $memcache_bins originate from settings.php.
- // $memcache_servers_custom and $memcache_bins_custom get set by
- // memcache.module. They are then merged into $memcache_servers and
- // $memcache_bins, which are statically cached for performance.
if (empty($memcache_servers)) {
- // Values from settings.php
+ // Load the variables from settings.php if set.
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
- $memcache_bins = variable_get('memcache_bins', array('cache' => 'default'));
+ $memcache_bins = variable_get('memcache_bins', array('cache' => 'default'));
}
- // If there is no cluster for this bin in $memcache_bins, cluster is 'default'.
+ // If not manually set, default this cluster to 'default'.
$cluster = empty($memcache_bins[$bin]) ? 'default' : $memcache_bins[$bin];
- // If this bin isn't in our $memcache_bins configuration array, and the
- // 'default' cluster is already initialized, map the bin to 'cache' because
- // we always map the 'cache' bin to the 'default' cluster.
+ // If not manually set, map this bin to 'cache' which maps to the 'default'
+ // cluster.
if (empty($memcache_bins[$bin]) && !empty($memcacheCache['cache'])) {
$memcacheCache[$bin] = &$memcacheCache['cache'];
}
else {
- // Create a new Memcache object. Each cluster gets its own Memcache object.
- if ($extension == 'Memcached') {
- $memcache = new Memcached;
- $default_opts = array(
- Memcached::OPT_COMPRESSION => FALSE,
- Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
- );
- foreach ($default_opts as $key => $value) {
- $memcache->setOption($key, $value);
- }
- // See README.txt for setting custom Memcache options when using the
- // memcached PECL extension.
- $memconf = variable_get('memcache_options', array());
- foreach ($memconf as $key => $value) {
- $memcache->setOption($key, $value);
- }
- }
- elseif ($extension == 'Memcache') {
- $memcache = new Memcache;
- }
- else {
- drupal_set_message(t('You must enable the PECL memcached or memcache extension to use memcache.inc.'), 'error');
- return;
- }
- // A variable to track whether we've connected to the first server.
- $init = FALSE;
+ // Create a new memcache object for each cluster.
+ $memcache = dmemcache_instance();
- // Link all the servers to this cluster.
- foreach ($memcache_servers as $s => $c) {
- if ($c == $cluster && !isset($failed_connection_cache[$s])) {
- list($host, $port) = explode(':', $s);
-
- // Using the Memcache PECL extension.
- if ($memcache instanceof Memcache) {
- // Support unix sockets in the format 'unix:///path/to/socket'.
- if ($host == 'unix') {
- // When using unix sockets with Memcache use the full path for $host.
- $host = $s;
- // Port is always 0 for unix sockets.
- $port = 0;
- }
- // When using the PECL memcache extension, we must use ->(p)connect
- // for the first connection.
- if (!$init) {
- $track_errors = ini_set('track_errors', '1');
- $php_errormsg = '';
-
- if ($memcache_persistent && @$memcache->pconnect($host, $port)) {
- $init = TRUE;
- }
- elseif (!$memcache_persistent && @$memcache->connect($host, $port)) {
- $init = TRUE;
- }
+ // Track whether or not we've opened any memcache connections.
+ $connection = FALSE;
- if (!empty($php_errormsg)) {
- register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_object: !msg', array('!msg' => $php_errormsg), WATCHDOG_WARNING);
- $php_errormsg = '';
- }
- ini_set('track_errors', $track_errors);
- }
- else {
- $memcache->addServer($host, $port, $memcache_persistent);
- }
+ // Link all the servers to this cluster.
+ foreach ($memcache_servers as $server => $c) {
+ if ($c == $cluster && !isset($failed_connections[$server])) {
+ $rc = dmemcache_connect($memcache, $server, $connection);
+ if ($rc !== FALSE) {
+ // We've made at least one successful connection.
+ $connection = TRUE;
}
else {
- // Support unix sockets in the format 'unix:///path/to/socket'.
- if ($host == 'unix') {
- // Memcached expects just the path to the socket without the protocol
- $host = substr($s, 7);
- // Port is always 0 for unix sockets.
- $port = 0;
- }
- if ($memcache->addServer($host, $port) && !$init) {
- $init = TRUE;
- }
- }
-
- if (!$init) {
- // We can't use watchdog because this happens in a bootstrap phase
- // where watchdog is non-functional. Register a shutdown handler
- // instead so it gets recorded at the end of page load.
- register_shutdown_function('watchdog', 'memcache', 'Failed to connect to memcache server: !server', array('!server' => $s), WATCHDOG_ERROR);
- $failed_connection_cache[$s] = FALSE;
+ // Memcache connection failure. We can't log to watchdog directly
+ // because we're in an early Drupal bootstrap phase where watchdog
+ // is non-functional. Instead, register a shutdown handler so it
+ // gets recorded at the end of the page load.
+ register_shutdown_function('watchdog', 'memcache', 'Failed to connect to memcache server: !server', array('!server' => $server), WATCHDOG_ERROR);
+ $failed_connections[$server] = FALSE;
}
}
}
-
- if ($init) {
+ if ($connection) {
// Map the current bin with the new Memcache object.
$memcacheCache[$bin] = $memcache;
diff --git a/memcache.install b/memcache.install
index 347c339..5315145 100644
--- a/memcache.install
+++ b/memcache.install
@@ -9,42 +9,91 @@ function memcache_requirements($phase) {
$memcache = extension_loaded('memcache');
$memcached = extension_loaded('memcached');
if ($phase == 'install' || $phase == 'runtime') {
+ $requirements['memcache_extension']['title'] = $t('Memcache');
if (!$memcache && !$memcached) {
$requirements['memcache_extension']['severity'] = REQUIREMENT_ERROR;
- $requirements['memcache_extension']['title'] = $t('Extensions not available');
- $requirements['memcache_extension']['value'] = $t('Either the <a href="http://php.net/manual/en/book.memcache.php">memcache</a> or <a href="http://php.net/manual/en/book.memcached.php">memcached</a> extensions must be installed in order to use memcache integration.');
+ $requirements['memcache_extension']['value'] = $t('Required PHP extension not found. Install the <a href="http://php.net/manual/en/book.memcache.php">memcache</a> (recommended) or <a href="http://php.net/manual/en/book.memcached.php">memcached</a> extension.');
}
}
if ($phase == 'runtime') {
- if ($memcache) {
- // @todo: consider adding minimum version requirement for extensions.
- $requirements['memcache_extension_version']['severity'] = REQUIREMENT_OK;
- $requirements['memcache_extension_version']['title'] = $t('Memcache version');
- $requirements['memcache_extension_version']['value'] = phpversion('memcache');
+ $errors = array();
+ if (!$memcache && !$memcached) {
+ $errors[] = $t('Required PHP extension not found. Install the <a href="http://php.net/manual/en/book.memcache.php">memcache</a> (recommended) or <a href="http://php.net/manual/en/book.memcached.php">memcached</a> extension.');
+ }
+
+ if (!function_exists('dmemcache_set')) {
+ // dmemcache.inc isn't loaded.
+ $errors[] = $t('Failed to load required file %dmemcache.', array('%dmemcache' => drupal_get_path('module', 'memcache') . '/' . 'dmemcache.inc'));
}
- if ($memcached) {
- $requirements['memcached_extension_version']['severity'] = REQUIREMENT_OK;
- $requirements['memcached_extension_version']['title'] = $t('Memcached version');
- $requirements['memcached_extension_version']['value'] = phpversion('memcached');
+ else {
+ $extension = dmemcache_extension();
+ if ($extension == 'Memcache') {
+ $requirements['memcache_extension']['value'] = phpversion('memcache') . _memcache_statistics_link();
+ }
+ else if ($extension == 'Memcached') {
+ $requirements['memcache_extension']['value'] = phpversion('memcached') . _memcache_statistics_link();
+ }
+
+ // Make a test connection to all configured memcache servers.
+ $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
+ $memcache = dmemcache_instance();
+ foreach ($memcache_servers as $server => $bin) {
+ if (dmemcache_connect($memcache, $server, FALSE) === FALSE) {
+ $errors[] = $t('Failed to connect to memcached server instance at %server.', array('%server' => $server));
+ }
+ else {
+ dmemcache_close($memcache);
+ }
+ }
+
+ // Set up a temporary bin to see if we can store a value and then
+ // successfully retreive it.
+ try {
+ $bin = 'memcache_requirements';
+ $cid = 'memcache_requirements_check';
+ $value = 'OK';
+ // Create a temporary memcache cache bin.
+ $GLOBALS['conf']["cache_class_cache_{$bin}"] = 'MemCacheDrupal';
+ // Store a test value in memcache.
+ cache_set($cid, $value, $bin, 60);
+ // Retreive the test value from memcache.
+ $data = cache_get($cid, $bin);
+ if (!isset($data->data) || $data->data !== $value) {
+ $errors[] = $t('Failed to store and retreive data with memcache.');
+ }
+ }
+ catch (Exception $e) {
+ // An unexpected exception occurred.
+ $errors[] = $t('Unexpected failure when connecting to memcache.');
+ }
}
- // Confirm that dmemcache.inc has been included.
- $requirements['memcache_inc']['title'] = $t('Memcache integration');
- if (function_exists('dmemcache_set')) {
- $requirements['memcache_inc']['severity'] = REQUIREMENT_OK;
- $requirements['memcache_inc']['title'] = $t('Memcache integration');
- $requirements['memcache_inc']['value'] = $t('Memcache integration functions are loaded');
+
+ if (empty($errors)) {
+ $requirements['memcache_extension']['severity'] = REQUIREMENT_OK;
}
else {
- $requirements['memcache_inc']['severity'] = REQUIREMENT_WARNING;
- $requirements['memcache_inc']['title'] = $t('Memcache integration');
- $requirements['memcache_inc']['value'] = $t('Memcache integration is not currently loaded.');
- $requirements['memcache_inc']['description'] = $t('Check README.txt and ensure that memcache.inc is configured correctly in settings.php');
+ $requirements['memcache_extension']['severity'] = REQUIREMENT_ERROR;
+ $requirements['memcache_extension']['description'] = $t('There is a problem with your memcache configuration, check the Drupal logs for additional errors. Please review %readme for help resolving the following !issue: !errors', array('%readme' => 'README.txt', '!issue' => format_plural(count($errors), 'issue', 'issues'), '!errors' => '<ul><li>' . implode('<li>', $errors)));
}
}
return $requirements;
}
/**
+ * Add "(more information)" link after memcache version if memcache_admin
+ * module is enabled and user has access to memcache statistics.
+ */
+function _memcache_statistics_link() {
+ $t = get_t();
+ if (module_exists('memcache_admin') && user_access('access memcache statistics')) {
+ return ' (' . l($t('more information'), 'admin/reports/memcache') . ')';
+ }
+ else {
+ return '';
+ }
+}
+
+/**
* Remove the memcache_widlcard_flushes variable since its structure has changed.
*/
function memcache_update_7000() {
diff --git a/memcache.module b/memcache.module
index 92f76a3..f70a91b 100644
--- a/memcache.module
+++ b/memcache.module
@@ -4,3 +4,31 @@
* memcache.inc must be configured in settings.php, and memcache.module is not
* necessary to use memcache as a cache backend.
*/
+function memcache_enable() {
+ $error = FALSE;
+ $memcache = extension_loaded('memcache');
+ $memcached = extension_loaded('memcached');
+ if (!$memcache && !$memcached) {
+ $error = TRUE;
+ }
+ if (!function_exists('dmemcache_object')) {
+ // dmemcache.inc isn't loaded.
+ $error = TRUE;
+ }
+ else {
+ // Make a test connection to all configured memcache servers.
+ $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
+ $memcache = dmemcache_instance();
+ foreach ($memcache_servers as $server => $bin) {
+ if (dmemcache_connect($memcache, $server, FALSE) === FALSE) {
+ $error = TRUE;
+ }
+ else {
+ dmemcache_close($memcache);
+ }
+ }
+ }
+ if ($error) {
+ drupal_set_message(t('There are problems with your Memcache configuration. Please review %readme and visit the Drupal admin !status page for more information.', array('%readme' => 'README.txt', '!status' => l('status report', 'admin/reports/status'))), 'error');
+ }
+}