You are here

memcache.install in Memcache API and Integration 7

Install, update and uninstall functions for the memcache module.

File

memcache.install
View source
<?php

/**
 * @file
 * Install, update and uninstall functions for the memcache module.
 */
define('MEMCACHE_PECL_RECOMMENDED', '3.0.6');
define('MEMCACHE_PECL_PHP7_RECOMMENDED', '4.0.5');
define('MEMCACHED_PECL_RECOMMENDED', '2.0.1');
define('MEMCACHED_ASCII_AUTH_MINIMUM', '1.5.15');
define('MEMCACHED_ASCII_AUTH_RECOMMENDED', '1.6.4');

/**
 * Implements hook_enable().
 */
function memcache_enable() {
  $error = FALSE;
  $warning = FALSE;
  $severity = 'status';
  $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 {
    if (!_memcache_pecl_version_valid()) {
      $warning = TRUE;
    }

    // If ASCII protocol authentication is enabled, perform extra tests.
    if (_dmemcache_use_ascii_auth()) {

      // Verify minimum required memcached version for ASCII protocol authentication is installed.
      $version = _memcached_ascii_auth_version_valid(MEMCACHED_ASCII_AUTH_MINIMUM);

      // TRUE means version valid, FALSE means we failed to connect and will throw a different error.
      if ($version !== TRUE && $version !== FALSE) {
        $error = TRUE;
      }
      else {
        $version = _memcached_ascii_auth_version_valid(MEMCACHED_ASCII_AUTH_RECOMMENDED);

        // TRUE means version valid, FALSE means we failed to connect and will throw a different error.
        if ($version !== TRUE && $version !== FALSE) {
          $warning = TRUE;
        }
      }
    }

    // Make a test connection to all configured memcache servers.
    $memcache_servers = variable_get('memcache_servers', array(
      '127.0.0.1:11211' => 'default',
    ));
    foreach ($memcache_servers as $server => $bin) {
      if ($cluster = dmemcache_object_cluster($bin)) {
        $memcache = dmemcache_instance($cluster['cluster']);
        if (dmemcache_connect($memcache, $server, $cluster['weight']) === FALSE) {
          $error = TRUE;
        }
        else {
          if (!variable_get('memcache_persistent', TRUE)) {
            dmemcache_close($memcache);
          }
        }
      }
    }
  }
  if ($error) {
    $severity = 'error';
  }
  elseif ($warning) {
    $severity = 'warning';
  }
  if ($error || $warning) {
    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(t('status report'), 'admin/reports/status'),
    )), $severity);
  }
}

/**
 * Implements hook_requirements().
 */
function memcache_requirements($phase) {
  $requirements = array();
  $t = get_t();
  $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']['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.');
    }
    else {
      $requirements['memcache_extension']['value'] = $t('PHP %extension Extension', array(
        '%extension' => $memcache ? $t('Memcache') : $t('Memcached'),
      ));
    }
  }
  if ($phase == 'runtime') {
    $errors = array();
    $warnings = 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',
      ));
      $requirements['memcache_extension']['value'] = $t('Unknown');
    }
    else {
      $extension = dmemcache_extension();
      if ($extension == 'Memcache') {
        $version = phpversion('memcache');
        if (version_compare(phpversion(), '7', '>=')) {
          $recommended = MEMCACHE_PECL_PHP7_RECOMMENDED;
        }
        else {
          $recommended = MEMCACHE_PECL_RECOMMENDED;
        }
      }
      elseif ($extension == 'Memcached') {
        $version = phpversion('memcached');
        $recommended = MEMCACHED_PECL_RECOMMENDED;
      }
      if (!$version) {
        $version = $t('Unknown');
      }
      $requirements['memcache_extension']['value'] = $version . _memcache_statistics_link();
      if (!_memcache_pecl_version_valid()) {
        $warnings[] = $t('PECL !extension version %version is unsupported. Please update to %recommended or newer.', array(
          '!extension' => $extension,
          '%version' => $version,
          '%recommended' => $recommended,
        ));
      }

      // If ASCII protocol authentication is enabled, perform extra tests.
      if (_dmemcache_use_ascii_auth()) {

        // Verify minimum required memcached version for ASCII protocol authentication is installed.
        $version = _memcached_ascii_auth_version_valid(MEMCACHED_ASCII_AUTH_MINIMUM);

        // TRUE means version valid, FALSE means we failed to connect and will throw a different error.
        if ($version !== TRUE && $version !== FALSE) {
          $errors[] = $t('ASCII protocol authentication is enabled but requires memcached v%minimum or greater. One or more memcached instances detected running memcache v%version.' . _memcache_statistics_link(), array(
            '%version' => $version,
            '%minimum' => MEMCACHED_ASCII_AUTH_MINIMUM,
          ));
        }
        else {
          $version = _memcached_ascii_auth_version_valid(MEMCACHED_ASCII_AUTH_RECOMMENDED);

          // TRUE means version valid, FALSE means we failed to connect and will throw a different error.
          if ($version !== TRUE && $version !== FALSE) {
            $warnings[] = $t('Memcached v%recommended is recommended when using ASCII protocol authentication, to avoid a CPU race. One or more memcached instances detected are running memcache v%version.' . _memcache_statistics_link(), array(
              '%version' => $version,
              '%recommended' => MEMCACHED_ASCII_AUTH_RECOMMENDED,
            ));
          }
        }

        // Confirm ASCII authentication works on all memcached servers.
        $memcache_bins = variable_get('memcache_bins', array(
          'cache' => 'default',
        ));
        foreach ($memcache_bins as $bin => $_) {
          if ($mc = dmemcache_object($bin)) {
            $ascii_auth = _check_ascii_auth($mc);
            if ($ascii_auth !== TRUE) {
              $s = $ascii_auth[0];
              $message = $ascii_auth[1];
              $errors[] = $t('ASCII protocol authentication failed: %message (%host:%port).' . _memcache_statistics_link(), array(
                '%host' => $s['host'],
                '%port' => $s['port'],
                '%message' => $message,
              ));
            }
          }
        }
      }

      // Make a test connection to all configured memcache servers.
      $memcache_servers = variable_get('memcache_servers', array(
        '127.0.0.1:11211' => 'default',
      ));
      foreach ($memcache_servers as $server => $bin) {
        if ($cluster = dmemcache_object_cluster($bin)) {
          $memcache = dmemcache_instance($cluster['cluster']);
          if (dmemcache_connect($memcache, $server, $cluster['weight']) === FALSE) {
            $errors[] = $t('Failed to connect to memcached server instance at %server.', array(
              '%server' => $server,
            ));
          }
          else {
            if (!variable_get('memcache_persistent', TRUE)) {
              dmemcache_close($memcache);
            }
          }
        }
      }

      // Set up a temporary bin to see if we can store a value and then
      // successfully retreive it.
      try {
        $cid = 'memcache_requirements_test';
        $value = 'OK';

        // Temporarily store a test value in memcache.
        cache_set($cid, $value);

        // Retreive the test value from memcache.
        $data = cache_get($cid);
        if (!isset($data->data) || $data->data !== $value) {
          $errors[] = $t('Failed to store to then retrieve data from memcache.');
        }
        else {

          // Test a delete as well.
          cache_clear_all($cid, 'cache');
        }
      } catch (Exception $e) {

        // An unexpected exception occurred.
        $errors[] = $t('Unexpected failure when testing memcache configuration.');
      }

      // Core's lock implementation can cause worse performance together with
      // stampede protection. Plus, long keys will be truncated resulting in
      // dropped locks.
      if (variable_get('memcache_stampede_protection', FALSE) && strpos(variable_get('lock_inc', 'includes/lock.inc'), 'includes/lock.inc') !== FALSE) {
        $warnings[] = $t('Drupal\'s core lock implementation (%core) is not supported by memcache stampede protection. Enable the memcache lock implementation (%memcache) or disable memcache stampede protection.', array(
          '%core' => 'includes/lock.inc',
          '%memcache' => drupal_get_path('module', 'memcache') . "/memcache-lock.inc",
        ));
      }
    }
    if (!empty($errors)) {

      // Error takes precedence over warning, stack together errors and
      // warnings and display as error messages.
      $errors = array_merge($errors, $warnings);
      unset($warnings);
      $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' => drupal_get_path('module', 'memcache') . '/' . 'README.txt',
        '!issue' => format_plural(count($errors), 'issue', 'issues'),
        '!errors' => '<ul><li>' . implode('<li>', $errors),
      ));
    }
    elseif (!empty($warnings)) {
      $requirements['memcache_extension']['severity'] = REQUIREMENT_WARNING;
      $requirements['memcache_extension']['description'] = $t('There is a problem with your memcache configuration. Please review %readme for help resolving the following !issue: !warnings', array(
        '%readme' => drupal_get_path('module', 'memcache') . '/' . 'README.txt',
        '!issue' => format_plural(count($warnings), 'issue', 'issues'),
        '!warnings' => '<ul><li>' . implode('<li>', $warnings),
      ));
    }
    else {
      $requirements['memcache_extension']['severity'] = REQUIREMENT_OK;
    }
  }
  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 '';
  }
}

/**
 * Validate whether the current PECL version is supported.
 */
function _memcache_pecl_version_valid() {
  $extension = dmemcache_extension();
  if ($extension == 'Memcache') {

    // The latest PECL Memcache releases are using a 4 digit version
    // which version_compare doesn't support. At this time the 4th
    // digit isn't important to us, so we just look at the first 3
    // digits. If a future release requires 4 digit precision
    // it will require a replacement for version_compare().
    $full_version = phpversion('memcache');
    $version = implode('.', array_slice(explode('.', $full_version), 0, 3));
    if (version_compare(phpversion(), '7' . '>=')) {
      return version_compare($version, MEMCACHE_PECL_PHP7_RECOMMENDED, '>=');
    }
    else {
      return version_compare($version, MEMCACHE_PECL_RECOMMENDED, '>=');
    }
  }
  elseif ($extension == 'Memcached') {
    return version_compare(phpversion('memcached'), MEMCACHED_PECL_RECOMMENDED, '>=');
  }
}

/**
 * If ASCII protocol authentication is enabled, validate whether the current memcached
 * version meets the minimum and/or recommended requirements.
 */
function _memcached_ascii_auth_version_valid($version) {
  if (_dmemcache_use_ascii_auth()) {
    $stats = dmemcache_stats();
    foreach ($stats as $bin => $servers) {
      if (!empty($servers)) {
        foreach ($servers as $server => $value) {
          $installed_version = $value['version'];
          if (version_compare($installed_version, $version, '<')) {

            // Return detected invalid version of memcached.
            return $installed_version;
          }
        }
      }
      else {
        return FALSE;
      }
    }
  }

  // Fall through if no unsupported version of memcached was detected, or ascii auth is
  // not enabled.
  return TRUE;
}

/**
 * Check that ascii authentication is setup correctly.
 *
 * @param string $memcache_original
 *   The memcache object whose servers will be checked.
 *
 * @return TRUE|array
 *   TRUE on success, the failed server and the error message
 *   on failure.
 */
function _check_ascii_auth($memcache_original) {
  $servers = $memcache_original
    ->getServerList();
  foreach ($servers as $s) {

    // Note: This intentionally does not use a persistent id.
    $mc = new Memcached();
    $mc
      ->addServer($s['host'], $s['port']);

    // Check that a set() / get() with a fresh connection fails.
    $mc
      ->set(DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY, TRUE);
    $rc = $mc
      ->get(DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY);
    if ($rc) {
      return [
        $s,
        t('ASCII authentication not enabled on server'),
      ];
    }
    $rc = _dmemcache_ensure_ascii_auth("0", $mc);
    if (!$rc) {
      return [
        $s,
        t('ASCII authentication failed'),
      ];
    }
  }
  return TRUE;
}

/**
 * Remove the memcache_widlcard_flushes variable since its structure has changed.
 */
function memcache_update_7000() {
  variable_del('memcache_wildcard_flushes');
}

Functions

Namesort descending Description
memcache_enable Implements hook_enable().
memcache_requirements Implements hook_requirements().
memcache_update_7000 Remove the memcache_widlcard_flushes variable since its structure has changed.
_check_ascii_auth Check that ascii authentication is setup correctly.
_memcached_ascii_auth_version_valid If ASCII protocol authentication is enabled, validate whether the current memcached version meets the minimum and/or recommended requirements.
_memcache_pecl_version_valid Validate whether the current PECL version is supported.
_memcache_statistics_link Add "(more information)" link after memcache version if memcache_admin module is enabled and user has access to memcache statistics.

Constants