You are here

dmemcache.inc in Memcache API and Integration 7

Same filename and directory in other branches
  1. 5.2 dmemcache.inc
  2. 5 dmemcache.inc
  3. 6 dmemcache.inc

A memcache API for Drupal.

This file contains core dmemcache functions required by: memcache.inc memcache-lock.inc memcache-lock-code.inc

File

dmemcache.inc
View source
<?php

/**
 * @file
 * A memcache API for Drupal.
 *
 * This file contains core dmemcache functions required by:
 *   memcache.inc
 *   memcache-lock.inc
 *   memcache-lock-code.inc
 */
define('MEMCACHED_E2BIG', 37);
define('MEMCACHED_CLIENT_ERROR', 9);
define('MEMCACHED_SERVER_TIMED_OUT', 47);
define('DRUPAL_MEMCACHE_ASCII_AUTH_KEY', 'drupal:memcache:ascii:auth:key');
define('DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY', 'drupal:memcache:ascii:auth:lifetime:key');
global $_dmemcache_stats;
$_dmemcache_stats = array(
  'all' => array(),
  'ops' => array(),
);

/**
 * Place an item into memcache.
 *
 * @param string $key
 *   The string with which you will retrieve this item later.
 * @param mixed $value
 *   The item to be stored.
 * @param int $exp
 *   Parameter expire is expiration time in seconds. If it's 0, the item never
 *   expires (but memcached server doesn't guarantee this item to be stored all
 *   the time, it could be deleted from the cache to make place for other
 *   items).
 * @param string $bin
 *   The name of the Drupal subsystem that is making this call. Examples could
 *   be 'cache', 'alias', 'taxonomy term' etc. It is possible to map different
 *   $bin values to different memcache servers.
 * @param object $mc
 *   Optionally pass in the memcache object.  Normally this value is determined
 *   automatically based on the bin the object is being stored to.
 *
 * @return bool
 *   TRUE on succes, FALSE otherwise.
 */
function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
  $collect_stats = dmemcache_stats_init();
  $full_key = dmemcache_key($key, $bin);
  $rc = FALSE;
  if ($mc || ($mc = dmemcache_object($bin))) {
    if ($mc instanceof Memcached) {
      $rc = $mc
        ->set($full_key, $value, $exp);
      if (empty($rc)) {
        if ($mc
          ->getResultCode() == MEMCACHED_CLIENT_ERROR && _dmemcache_ensure_ascii_auth($full_key, $mc)) {
          $rc = $mc
            ->set($full_key, $value, $exp);
        }

        // If there was a MEMCACHED_E2BIG error, split the value into pieces
        // and cache them individually.
        if ($mc
          ->getResultCode() == MEMCACHED_E2BIG) {
          $rc = _dmemcache_set_pieces($key, $value, $exp, $bin, $mc);
        }
      }
    }
    else {

      // The PECL Memcache library throws an E_NOTICE level error, which
      // $php_errormsg doesn't catch, so we need to log it ourselves.
      // Catch it with our own error handler.
      drupal_static_reset('_dmemcache_error_handler');
      set_error_handler('_dmemcache_error_handler');
      $rc = $mc
        ->set($full_key, $value, MEMCACHE_COMPRESSED, $exp);

      // Restore the Drupal error handler.
      restore_error_handler();
      if (empty($rc)) {

        // If the object was too big, split the value into pieces and cache
        // them individually.
        $dmemcache_errormsg =& drupal_static('_dmemcache_error_handler');
        if (!empty($dmemcache_errormsg) && (strpos($dmemcache_errormsg, 'SERVER_ERROR object too large for cache') !== FALSE || strpos($dmemcache_errormsg, 'SERVER_ERROR out of memory storing object') !== FALSE)) {
          $rc = _dmemcache_set_pieces($key, $value, $exp, $bin, $mc);
        }
      }
    }
  }
  if ($collect_stats) {
    dmemcache_stats_write('set', $bin, array(
      $full_key => $rc,
    ));
  }
  _dmemcache_write_debug('set', $bin, $full_key, $rc);
  return $rc;
}

/**
 * A temporary error handler which keeps track of the most recent error.
 */
function _dmemcache_error_handler($errno, $errstr) {
  $dmemcache_errormsg =& drupal_static(__FUNCTION__);
  $dmemcache_errormsg = $errstr;
  return TRUE;
}

/**
 *  Split a large item into pieces and place them into memcache
 *
 * @param string $key
 *   The string with which you will retrieve this item later.
 * @param mixed $value
 *   The item to be stored.
 * @param int $exp
 *   (optional) Expiration time in seconds. If it's 0, the item never expires
 *   (but memcached server doesn't guarantee this item to be stored all the
 *   time, it could be deleted from the cache to make place for other items).
 * @param string $bin
 *   (optional) The name of the Drupal subsystem that is making this call.
 *   Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible to
 *   map different $bin values to different memcache servers.
 * @param object $mc
 *   (optional) The memcache object. Normally this value is
 *   determined automatically based on the bin the object is being stored to.
 *
 * @return bool
 */
function _dmemcache_set_pieces($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
  static $recursion = 0;
  if (!empty($value->multi_part_data) || !empty($value->multi_part_pieces)) {

    // Prevent an infinite loop.
    return FALSE;
  }

  // Recursion happens when __dmemcache_piece_cache outgrows the largest
  // memcache slice (1 MiB by default) -- prevent an infinite loop and later
  // generate a watchdog error.
  if ($recursion) {
    return FALSE;
  }
  $recursion++;
  $full_key = dmemcache_key($key);

  // Cache the name of this key so if it is deleted later we know to also
  // delete the cache pieces.
  if (!dmemcache_piece_cache_set($full_key, $exp)) {

    // We're caching a LOT of large items. Our piece_cache has exceeded the
    // maximum memcache object size (default of 1 MiB).
    $piece_cache =& drupal_static('dmemcache_piece_cache', array());
    register_shutdown_function('watchdog', 'memcache', 'Too many over-sized cache items (!count) has caused the dmemcache_piece_cache to exceed the maximum memcache object size (default of 1 MiB). Now relying on memcache auto-expiration to eventually clean up over-sized cache pieces upon deletion.', array(
      '!count' => count($piece_cache),
    ), WATCHDOG_ERROR);
  }
  if (variable_get('memcache_log_data_pieces', 2)) {
    timer_start('memcache_split_data');
  }

  // We need to split the item into pieces, so convert it into a string.
  if (is_string($value)) {
    $data = $value;
    $serialized = FALSE;
  }
  else {
    $serialize_function = dmemcache_serialize();
    $data = $serialize_function($value);
    $serialized = TRUE;
  }

  // Account for any metadata stored alongside the data.
  $max_len = variable_get('memcache_data_max_length', 1048576) - (512 + strlen($full_key));
  $pieces = str_split($data, $max_len);
  $piece_count = count($pieces);

  // Create a placeholder item containing data about the pieces.
  $cache = new stdClass();

  // $key gets run through dmemcache_key() later inside dmemcache_set().
  $cache->cid = $key;
  $cache->created = REQUEST_TIME;
  $cache->expire = $exp;
  $cache->data = new stdClass();
  $cache->data->serialized = $serialized;
  $cache->data->piece_count = $piece_count;
  $cache->multi_part_data = TRUE;
  $result = dmemcache_set($cache->cid, $cache, $exp, $bin, $mc);

  // Create a cache item for each piece of data.
  foreach ($pieces as $id => $piece) {
    $cache = new stdClass();
    $cache->cid = _dmemcache_key_piece($key, $id);
    $cache->created = REQUEST_TIME;
    $cache->expire = $exp;
    $cache->data = $piece;
    $cache->multi_part_piece = TRUE;
    $result &= dmemcache_set($cache->cid, $cache, $exp, $bin, $mc);
  }
  if (variable_get('memcache_log_data_pieces', 2) && $piece_count >= variable_get('memcache_log_data_pieces', 2)) {
    if (function_exists('format_size')) {
      $data_size = format_size(strlen($data));
    }
    else {
      $data_size = number_format(strlen($data)) . ' byte';
    }
    register_shutdown_function('watchdog', 'memcache', 'Spent !time ms splitting !bytes object into !pieces pieces, cid = !key', array(
      '!time' => timer_read('memcache_split_data'),
      '!bytes' => $data_size,
      '!pieces' => $piece_count,
      '!key' => dmemcache_key($key, $bin),
    ), WATCHDOG_WARNING);
  }
  $recursion--;

  // TRUE if all pieces were saved correctly.
  return $result;
}

/**
 * Add an item into memcache.
 *
 * @param string $key
 *   The string with which you will retrieve this item later.
 * @param mixed $value
 *   The item to be stored.
 * @param int $exp
 *   (optional) Expiration time in seconds. If it's 0, the item never expires
 *   (but memcached server doesn't guarantee this item to be stored all the
 *   time, it could be deleted from the cache to make place for other items).
 * @param string $bin
 *   (optional) The name of the Drupal subsystem that is making this call.
 *   Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible
 *   to map different $bin values to different memcache servers.
 * @param object $mc
 *   (optional) The memcache object. Normally this value is
 *   determined automatically based on the bin the object is being stored to.
 * @param bool $flag
 *   (optional) If using the older memcache PECL extension as opposed to the
 *   newer memcached PECL extension, the MEMCACHE_COMPRESSED flag can be set
 *   to use zlib to store a compressed copy of the item.  This flag option is
 *   completely ignored when using the newer memcached PECL extension.
 *
 * @return bool
 *   FALSE if placing the item into memcache failed.
 */
function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag = FALSE) {
  $collect_stats = dmemcache_stats_init();
  $full_key = dmemcache_key($key, $bin);
  $rc = FALSE;
  if ($mc || ($mc = dmemcache_object($bin))) {
    if ($mc instanceof Memcached) {
      $rc = $mc
        ->add($full_key, $value, $exp);
      if (empty($rc) && $mc
        ->getResultCode() == MEMCACHED_CLIENT_ERROR && _dmemcache_ensure_ascii_auth($full_key, $mc)) {
        $rc = $mc
          ->add($full_key, $value, $exp);
      }
    }
    else {
      $rc = $mc
        ->add($full_key, $value, $flag, $exp);
    }
  }
  if ($collect_stats) {
    dmemcache_stats_write('add', $bin, array(
      $full_key => $rc,
    ));
  }
  _dmemcache_write_debug('add', $bin, $full_key, $rc);
  return $rc;
}

/**
 * Retrieve a value from the cache.
 *
 * @param string $key
 *   The key with which the item was stored.
 * @param string $bin
 *   The bin in which the item was stored.
 *
 * @return mixed
 *   The item which was originally saved or FALSE
 */
function dmemcache_get($key, $bin = 'cache', $mc = NULL) {
  static $memcache_ascii_auth = NULL;
  $collect_stats = dmemcache_stats_init();
  $result = FALSE;
  $full_key = dmemcache_key($key, $bin);
  if ($mc || ($mc = dmemcache_object($bin))) {
    $track_errors = ini_set('track_errors', '1');
    $php_errormsg = '';
    $result = @$mc
      ->get($full_key);
    if (empty($result)) {
      if (!isset($memcache_ascii_auth)) {
        $memcache_ascii_auth = _dmemcache_use_ascii_auth();
      }
      if ($memcache_ascii_auth) {

        // As sets are costly we first check that the server is not authorized first.
        $rc = $mc
          ->getByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY);
        if (empty($rc) && _dmemcache_ensure_ascii_auth($full_key, $mc)) {
          $result = @$mc
            ->get($full_key);
        }
      }
    }

    // This is a multi-part value.
    if (is_object($result) && !empty($result->multi_part_data)) {
      $result = _dmemcache_get_pieces($result->data, $result->cid, $bin, $mc);
    }
    if (!empty($php_errormsg)) {
      register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_get: !msg', array(
        '!msg' => $php_errormsg,
      ), WATCHDOG_WARNING);
      $php_errormsg = '';
    }
    ini_set('track_errors', $track_errors);
  }
  if ($collect_stats) {
    dmemcache_stats_write('get', $bin, array(
      $full_key => (bool) $result,
    ));
  }
  _dmemcache_write_debug('get', $bin, $full_key, (bool) $result);
  return $result;
}

/**
 * Retrieve a value from the cache.
 *
 * @param $item
 *   The placeholder cache item from _dmemcache_set_pieces().
 * @param $key
 *   The key with which the item was stored.
 * @param string $bin
 *   (optional) The bin in which the item was stored.
 * @param object $mc
 *   (optional) The memcache object. Normally this value is
 *   determined automatically based on the bin the object is being stored to.
 *
 * @return object|bool
 *   The item which was originally saved or FALSE.
 */
function _dmemcache_get_pieces($item, $key, $bin = 'cache', $mc = NULL) {

  // Create a list of keys for the pieces of data.
  for ($id = 0; $id < $item->piece_count; $id++) {
    $keys[] = _dmemcache_key_piece($key, $id);
  }

  // Retrieve all the pieces of data and append them together.
  $pieces = dmemcache_get_multi($keys, $bin, $mc);
  $data = '';
  foreach ($pieces as $piece) {

    // The piece may be NULL if it didn't exist in memcache. If so,
    // we have to just return false for the entire set because we won't
    // be able to reconstruct the original data without all the pieces.
    if (!$piece) {
      return FALSE;
    }
    $data .= $piece->data;
  }
  unset($pieces);

  // If necessary unserialize the item.
  if (empty($item->serialized)) {
    return $data;
  }
  else {
    $unserialize_function = dmemcache_unserialize();
    return $unserialize_function($data);
  }
}

/**
 * Generates a key name for a multi-part data piece based on the sequence ID.
 *
 * @param int $id
 *   The sequence ID of the data piece.
 * @param int $key
 *   The original CID of the cache item.
 *
 * @return string
 */
function _dmemcache_key_piece($key, $id) {
  return dmemcache_key('_multi' . (string) $id . "-{$key}");
}

/**
 * Retrieve multiple values from the cache.
 *
 * @param array $keys
 *   The keys with which the items were stored.
 * @param string $bin
 *   The bin in which the item was stored.
 *
 * @return mixed
 *   An array keyed by $keys with the items requested or
 *   NULL values if the data was not present.
 */
function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) {
  static $memcache_ascii_auth = NULL;
  $collect_stats = dmemcache_stats_init();
  $multi_stats = array();
  $full_keys = array();
  foreach ($keys as $key => $cid) {
    $full_key = dmemcache_key($cid, $bin);
    $full_keys[$cid] = $full_key;
    if ($collect_stats) {
      $multi_stats[$full_key] = FALSE;
    }
  }
  $results = array();
  if ($mc || ($mc = dmemcache_object($bin))) {
    if ($mc instanceof Memcached) {
      if (PHP_MAJOR_VERSION >= 7) {

        // See https://www.drupal.org/node/2728427
        $results = $mc
          ->getMulti($full_keys, Memcached::GET_PRESERVE_ORDER);
      }
      else {
        $cas_tokens = NULL;
        $results = $mc
          ->getMulti($full_keys, $cas_tokens, Memcached::GET_PRESERVE_ORDER);
      }

      // If $results is FALSE, convert it to an empty array.
      if (!$results) {
        $results = array();
      }
      if (!isset($memcache_ascii_auth)) {
        $memcache_ascii_auth = _dmemcache_use_ascii_auth();
      }

      // Find out which results have values and which have not.
      if ($memcache_ascii_auth && count(array_filter($results)) < count($full_keys)) {

        // Convert empty results to the full keys with value of NULL.
        if (empty($results)) {
          $results = array_fill_keys($full_keys, NULL);
        }
        $new_results = _dmemcache_get_multi_2nd_chance_ascii_auth($results, $mc);
        foreach ($new_results as $full_key => $value) {
          $results[$full_key] = $value;
        }
      }
    }
    elseif ($mc instanceof Memcache) {
      $track_errors = ini_set('track_errors', '1');
      $php_errormsg = '';
      $results = @$mc
        ->get($full_keys);
      if (!empty($php_errormsg)) {
        register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_get_multi: !msg', array(
          '!msg' => $php_errormsg,
        ), WATCHDOG_WARNING);
        $php_errormsg = '';
      }
      ini_set('track_errors', $track_errors);

      // Order is not guaranteed, map responses to order of requests.
      if (is_array($results)) {
        $keys = array_fill_keys($full_keys, NULL);
        $results = array_merge($keys, $results);
      }
    }
  }

  // If $results is FALSE, convert it to an empty array.
  if (!$results) {
    $results = array();
    $keys = array_fill_keys($full_keys, NULL);
    $results = array_merge($keys, $results);
    _dmemcache_write_debug('getMulti', $bin, implode(',', $full_keys), FALSE);
  }
  if ($collect_stats) {
    foreach ($multi_stats as $key => $value) {
      $multi_stats[$key] = isset($results[$key]) ? TRUE : FALSE;
    }
  }

  // Convert the full keys back to the cid.
  $cid_results = array();
  $cid_lookup = array_flip($full_keys);
  foreach ($results as $key => $value) {

    // This is a multi-part value.
    if (is_object($value) && !empty($value->multi_part_data)) {
      $value = _dmemcache_get_pieces($value->data, $value->cid, $bin, $mc);
    }
    $cid_results[$cid_lookup[$key]] = $value;
    _dmemcache_write_debug('getMulti', $bin, $key, TRUE);
  }
  if ($collect_stats) {
    dmemcache_stats_write('getMulti', $bin, $multi_stats);
  }
  return $cid_results;
}

/**
 * Deletes an item from the cache.
 *
 * @param string $key
 *   The key with which the item was stored.
 * @param string $bin
 *   The bin in which the item was stored.
 *
 * @return bool
 *   Returns TRUE on success or FALSE on failure.
 */
function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
  $collect_stats = dmemcache_stats_init();
  $full_keys = dmemcache_key($key, $bin, TRUE);
  $rc = FALSE;
  if ($mc || ($mc = dmemcache_object($bin))) {
    foreach ($full_keys as $fk) {
      $rc = $mc
        ->delete($fk, 0);
      if (empty($rc) && $mc instanceof Memcached && _dmemcache_ensure_ascii_auth($fk, $mc)) {
        $rc = $mc
          ->delete($fk, 0);
      }
      if ($rc) {

        // If the delete succeeded, we now check to see if this item has
        //  multiple pieces also needing to be cleaned up. If the delete failed,
        // we assume these keys have already expired or been deleted (memcache
        // will auto-expire eventually if we're wrong).
        if ($piece_cache = dmemcache_piece_cache_get($fk)) {

          // First, remove from the piece_cache so we don't try and delete it
          // again in another thread, then delete the actual cache data pieces.
          dmemcache_piece_cache_set($fk, NULL);
          $next_id = 0;
          do {
            if ($inner_rc) {
              _dmemcache_write_debug("delete", $bin, $full_key, $inner_rc);
            }

            // Generate the cid of the next data piece.
            $piece_key = _dmemcache_key_piece($key, $next_id);
            $full_key = dmemcache_key($piece_key, $bin);
            $next_id++;

            // Keep deleting pieces until the operation fails. We accept that
            // this could lead to orphaned pieces as memcache will auto-expire
            // them eventually.
          } while ($inner_rc = $mc
            ->delete($full_key, 0));
          _dmemcache_write_debug("delete", $bin, $full_key, $inner_rc);

          // Perform garbage collection for keys memcache has auto-expired. If
          // we don't do this, this variable could grow over enough time as a
          // slow memory leak.
          // @todo: Consider moving this to hook_cron() and requiring people to
          // enable the memcache module.
          timer_start('memcache_gc_piece_cache');
          $gc_counter = 0;
          $piece_cache =& drupal_static('dmemcache_piece_cache', array());
          foreach ($piece_cache as $cid => $expires) {
            if (REQUEST_TIME > $expires) {
              $gc_counter++;
              dmemcache_piece_cache_set($cid, NULL);
            }
          }
          if ($gc_counter) {
            register_shutdown_function('watchdog', 'memcache', 'Spent !time ms in garbage collection cleaning !count stale keys from the dmemcache_piece_cache.', array(
              '!time' => timer_read('memcache_gc_piece_cache'),
              '!count' => $gc_counter,
            ), WATCHDOG_WARNING);
          }
        }
      }
      if ($collect_stats) {
        dmemcache_stats_write('delete', $bin, array(
          $fk => $rc,
        ));
      }
      _dmemcache_write_debug('delete', $bin, $fk, $rc);
    }
  }
  return $rc;
}

/**
 * Flush all stored items.
 *
 * Immediately invalidates all existing items. dmemcache_flush doesn't actually
 * free any resources, it only marks all the items as expired, so occupied
 * memory will be overwritten by new items.
 *
 * @param string $bin
 *   The bin to flush. Note that this will flush all bins mapped to the same
 *   server as $bin. There is no way at this time to empty just one bin.
 *
 * @return bool
 *   Returns TRUE on success or FALSE on failure.
 */
function dmemcache_flush($bin = 'cache', $mc = NULL) {
  $collect_stats = dmemcache_stats_init();
  $rc = FALSE;
  if ($mc || ($mc = dmemcache_object($bin))) {
    $rc = $mc
      ->flush();

    // If a composite call fails, we need to reset the authentication
    // for the whole mc object.
    if (empty($rc) && _dmemcache_reset_ascii_auth($mc)) {
      $mc
        ->flush();
    }
  }
  if ($collect_stats) {
    dmemcache_stats_write('flush', $bin, array(
      '' => $rc,
    ));
  }
  _dmemcache_write_debug('flush', $bin, '', $rc);
  return $rc;
}

/**
 * Retrieves statistics recorded during memcache operations.
 *
 * @param string $stats_bin
 *   The bin to retrieve statistics for.
 * @param string $stats_type
 *   The type of statistics to retrieve when using the Memcache extension.
 * @param bool $aggregate
 *   Whether to aggregate statistics.
 *
 * @return array
 *   The statistics
 */
function dmemcache_stats($stats_bin = 'cache', $stats_type = 'default', $aggregate = FALSE) {
  $memcache_bins = variable_get('memcache_bins', array(
    'cache' => 'default',
  ));

  // The stats_type can be over-loaded with an integer slab id, if doing a
  // cachedump.  We know we're doing a cachedump if $slab is non-zero.
  $slab = (int) $stats_type;
  $stats = array();
  foreach ($memcache_bins as $bin => $target) {
    if ($stats_bin == $bin) {
      if ($mc = dmemcache_object($bin)) {
        if ($mc instanceof Memcached) {
          $stats[$bin] = $mc
            ->getStats();

          // If a composite call fails, we need to reset the authentication
          // for the whole mc object.
          if (empty($stats[$bin]) && _dmemcache_reset_ascii_auth($mc)) {
            $stats[$bin] = $mc
              ->getStats();
          }
        }
        elseif ($mc instanceof Memcache) {
          if ($stats_type == 'default' || $stats_type == '') {
            $rc = $mc
              ->getExtendedStats();
            if (is_array($rc)) {
              foreach ($rc as $server => $data) {
                if (empty($data)) {
                  unset($rc[$server]);
                }
              }
              if (!empty($rc)) {
                $stats[$bin] = $rc;
              }
            }
          }
          elseif (!empty($slab)) {
            $stats[$bin] = $mc
              ->getStats('cachedump', $slab);
          }
          else {
            $stats[$bin] = $mc
              ->getExtendedStats($stats_type);
          }
        }
      }
    }
  }

  // Optionally calculate a sum-total for all servers in the current bin.
  if ($aggregate) {
    foreach ($stats as $bin => $servers) {
      if (is_array($servers)) {
        foreach ($servers as $server) {
          if (is_array($server)) {
            foreach ($server as $key => $value) {
              if (is_numeric($value)) {
                if (isset($stats[$bin]['total'][$key])) {
                  $stats[$bin]['total'][$key] += $value;
                }
                else {
                  $stats[$bin]['total'][$key] = $value;
                }
              }
            }
          }
        }
      }
    }
  }
  return $stats;
}

/**
 * Determine which memcache extension to use: memcache or memcached.
 *
 * By default prefer the 'Memcache' PHP extension, though the default can be
 * overridden by setting memcache_extension in settings.php.
 */
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, FALSE)) {
      $extension = ucfirst(strtolower($preferred));
    }
    elseif (class_exists('Memcache', FALSE)) {
      $extension = 'Memcache';
    }
    elseif (class_exists('Memcached', FALSE)) {
      $extension = 'Memcached';
    }
    else {
      $extension = FALSE;
    }
  }
  return $extension;
}

/**
 * Determine which serialize extension to use: serialize (none), igbinary,
 * or msgpack.
 *
 * By default we prefer the igbinary extension, then the msgpack extension,
 * then the core serialize functions. This can be overridden in settings.php.
 */
function dmemcache_serialize_extension() {
  static $extension = NULL;
  if ($extension === NULL) {
    $preferred = strtolower(variable_get('memcache_serialize', NULL));

    // Fastpath if we're forcing php's own serialize function.
    if ($preferred == 'serialize') {
      $extension = $preferred;
    }
    else {
      $igbinary_available = extension_loaded('igbinary');
      $msgpack_available = extension_loaded('msgpack');
      if ($preferred == 'igbinary' && $igbinary_available) {
        $extension = $preferred;
      }
      elseif ($preferred == 'msgpack' && $msgpack_available) {
        $extension = $preferred;
      }
      else {

        // No (valid) serialize extension specified, try igbinary.
        if ($igbinary_available) {
          $extension = 'igbinary';
        }
        else {
          if ($msgpack_available) {
            $extension = 'msgpack';
          }
          else {
            $extension = 'serialize';
          }
        }
      }
    }
  }
  return $extension;
}

/**
 * Return proper serialize function.
 */
function dmemcache_serialize() {
  switch (dmemcache_serialize_extension()) {
    case 'igbinary':
      return 'igbinary_serialize';
    case 'msgpack':
      return 'msgpack_pack';
    default:
      return 'serialize';
  }
}

/**
 * Return proper unserialize function.
 */
function dmemcache_unserialize() {
  switch (dmemcache_serialize_extension()) {
    case 'igbinary':
      return 'igbinary_unserialize';
    case 'msgpack':
      return 'msgpack_unpack';
    default:
      return 'unserialize';
  }
}

/**
 * Return a new memcache instance.
 */
function dmemcache_instance($bin = 'cache') {
  static $error = FALSE;
  $extension = dmemcache_extension();
  if ($extension == 'Memcache') {
    return new Memcache();
  }
  elseif ($extension == 'Memcached') {
    if (variable_get('memcache_persistent', TRUE)) {
      $memcache = new Memcached($bin);
    }
    else {
      $memcache = new Memcached();
    }
    $default_opts = array(
      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);
    }
    if (($sasl_username = variable_get('memcache_sasl_username', '')) && ($sasl_password = variable_get('memcache_sasl_password', ''))) {
      $memcache
        ->setSaslAuthData($sasl_username, $sasl_password);
    }
    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 object $memcache
 *   A memcache instance obtained through dmemcache_instance.
 * @param string $server
 *   A server string of the format "localhost:11211" or
 *   "unix:///path/to/socket".
 * @param integer $weight
 *   Weighted probability of talking to this server.
 *
 * @return bool
 *   TRUE or FALSE if connection was successful.
 */
function dmemcache_connect($memcache, $server, $weight) {
  static $memcache_persistent = NULL;
  static $memcache_ascii_auth = NULL;
  $extension = dmemcache_extension();
  @(list($host, $port) = explode(':', trim($server)));
  if (empty($host)) {
    register_shutdown_function('watchdog', 'memcache', 'You have specified an invalid address of "!server" in settings.php. Please review README.txt for proper configuration.', array(
      '!server' => $server,
      '!ip' => t('127.0.0.1:11211'),
      '!host' => t('localhost:11211'),
      '!socket' => t('unix:///path/to/socket'),
    ), WATCHDOG_WARNING);
  }
  if (!isset($memcache_persistent)) {
    $memcache_persistent = variable_get('memcache_persistent', TRUE);
  }
  if (!isset($memcache_ascii_auth)) {
    $memcache_ascii_auth = _dmemcache_use_ascii_auth();
  }
  $port_error = FALSE;
  if ($extension == 'Memcache') {

    // 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;
    }
    else {
      if (!isset($port)) {
        $port_error = TRUE;
      }
    }
  }
  elseif ($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;
    }
    else {
      if (!isset($port)) {
        $port_error = TRUE;
      }
    }
  }
  if ($port_error) {
    register_shutdown_function('watchdog', 'memcache', 'You have specified an invalid address of "!server" in settings.php which does not include a port. Please review README.txt for proper configuration. You must specify both a server address and port such as "!ip" or "!host", or a unix socket such as "!socket".', array(
      '!server' => $server,
      '!ip' => t('127.0.0.1:11211'),
      '!host' => t('localhost:11211'),
      '!socket' => t('unix:///path/to/socket'),
    ), WATCHDOG_WARNING);
  }
  if ($extension == 'Memcache') {
    $rc = $memcache
      ->addServer($host, $port, $memcache_persistent, $weight);
  }
  elseif ($extension == 'Memcached') {
    $match = FALSE;
    if ($memcache_persistent) {
      $servers = $memcache
        ->getServerList();
      foreach ($servers as $s) {
        if ($s['host'] == $host && $s['port'] == $port) {
          $match = TRUE;
          break;
        }
      }
    }
    if (!$match) {
      $rc = $memcache
        ->addServer($host, $port);
      if ($rc && $memcache_ascii_auth) {
        $rc = _dmemcache_ascii_authenticate_server($host, $port, $memcache);
      }
    }
    else {
      $rc = TRUE;
    }
  }
  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;
  }
  elseif ($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 string $bin
 *   The bin which is to be used.
 * @param bool $reset
 *   If TRUE will reset all static caches. Defaults to FALSE.
 *   To force a reconnection to memcached, first call $mc->quit() or
 *   $mc->close() (as appropriate for your PECL extension).
 *
 * @return mixed
 *   A Memcache object, or FALSE on failure.
 */
function dmemcache_object($bin = NULL, $reset = FALSE) {
  static $memcache_cache = array();
  static $memcache_servers = array();
  static $memcache_bins = array();
  static $failed_connections = array();
  if ($reset) {
    $memcache_cache = array();
    $memcache_servers = array();
    $memcache_bins = array();
    $failed_connections = array();
  }
  if (empty($memcache_cache) || empty($memcache_cache[$bin])) {
    if (empty($memcache_servers)) {

      // 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',
      ));
    }

    // If not manually set, default this cluster to 'default'.
    $cluster = empty($memcache_bins[$bin]) ? 'default' : $memcache_bins[$bin];

    // If not manually set, map this bin to 'cache' which maps to the 'default'
    // cluster.
    if (empty($memcache_bins[$bin]) && !empty($memcache_cache['cache'])) {
      $memcache_cache[$bin] =& $memcache_cache['cache'];
    }
    else {

      // Create a new memcache object for each cluster.
      $memcache = dmemcache_instance($bin);

      // Track whether or not we've opened any memcache connections.
      $connection = FALSE;

      // Link all the servers to this cluster.
      foreach ($memcache_servers as $server => $b) {
        if ($c = dmemcache_object_cluster($b)) {
          if ($c['cluster'] == $cluster && !isset($failed_connections[$server])) {
            $rc = dmemcache_connect($memcache, $server, $c['weight'], $connection);
            if ($rc) {

              // We've made at least one connection.
              $connection = TRUE;
            }
            else {

              // 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 ($connection) {

        // Map the current bin with the new Memcache object.
        $memcache_cache[$bin] = $memcache;

        // Now that all the servers have been mapped to this cluster, look for
        // other bins that belong to the cluster and map them too.
        foreach ($memcache_bins as $b => $c) {
          if ($c == $cluster && $b != $bin) {

            // Map this bin and cluster by reference.
            $memcache_cache[$b] =& $memcache_cache[$bin];
          }
        }
      }
    }
  }
  return empty($memcache_cache[$bin]) ? FALSE : $memcache_cache[$bin];
}

/**
 * Ensure that we're working with a proper cluster array.
 */
function dmemcache_object_cluster($cluster) {
  if (!is_array($cluster)) {

    // Set defaults.
    $cluster = array(
      'cluster' => $cluster,
      'weight' => 1,
    );
  }
  if (!isset($cluster['cluster']) || !is_string($cluster['cluster'])) {

    // Cluster is required, complain if it's missing or invalid.
    register_shutdown_function('watchdog', 'memcache', 'Ignoring invalid or missing cluster definition, review your memcache_servers configuration.', array(), WATCHDOG_ERROR);
    return FALSE;
  }
  if (!isset($cluster['weight']) || !is_int($cluster['weight']) || $cluster['weight'] < 1) {

    // Weight is optional.
    $cluster['weight'] = 1;
  }
  return $cluster;
}

/**
 * Prefixes a key and ensures it is url safe.
 *
 * @param string $key
 *   The key to prefix and encode.
 * @param string $bin
 *   The cache bin which the key applies to.
 * @param string $multiple
 *   If TRUE will return all possible prefix variations.
 *
 * @return string or array
 *   The prefixed and encoded key(s).
 */
function dmemcache_key($key, $bin = 'cache', $multiple = FALSE) {
  $prefix = '';
  if ($prefixes = variable_get('memcache_key_prefix', '')) {
    if (is_array($prefixes)) {

      // If no custom prefix defined for bin, use 'default'.
      if (empty($prefixes[$bin])) {
        $bin = 'default';
      }
      if (!empty($prefixes[$bin])) {

        // There can be multiple prefixes specified for each bin.
        if (is_array($prefixes[$bin])) {

          // Optionally return key with all prefixes.
          if ($multiple) {
            $prefix = array();
            foreach ($prefixes[$bin] as $pre) {
              $prefix[] = $pre . '-';
            }
          }
          else {
            $prefix = $prefixes[$bin][0] . '-';
          }
        }
      }
    }
    else {
      $prefix = $prefixes . '-';
    }
  }
  if (!is_array($prefix)) {
    $prefix = array(
      $prefix,
    );
  }
  $full_keys = array();
  foreach ($prefix as $p) {

    // When simpletest is running, emulate the simpletest database prefix here
    // to avoid the child site setting cache entries in the parent site.
    if (isset($GLOBALS['drupal_test_info']['test_run_id'])) {
      $p .= $GLOBALS['drupal_test_info']['test_run_id'];
    }
    $full_keys[] = urlencode($p . $bin . '-' . $key);
  }

  // Memcache truncates keys longer than 250 characters[*]. This could lead to
  // cache collisions, so we hash keys that are longer than this while still
  // retaining as much of the key bin and name as possible to aid in debugging.
  // The hashing algorithm used is configurable, with sha1 selected by default
  // as it performs quickly with minimal collisions. You can enforce shorter
  // keys by setting memcache_key_max_length in settings.php.
  // [*]https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L47
  $maxlen = variable_get('memcache_key_max_length', 250);
  foreach ($full_keys as $k => $full_key) {
    if (strlen($full_key) > $maxlen) {
      $full_keys[$k] = urlencode($prefix[$k] . $bin) . '-' . hash(variable_get('memcache_key_hash_algorithm', 'sha1'), $key);
      $full_keys[$k] .= '-' . substr(urlencode($key), 0, $maxlen - 1 - strlen($full_keys[$k]) - 1);
    }
  }
  if ($multiple) {

    // An array of prefixed keys.
    return $full_keys;
  }
  else {

    // A single prefixed key.
    return array_shift($full_keys);
  }
}

/**
 * Track active keys with multi-piece values, necessary for efficient cleanup.
 *
 * We can't use variable_get/set for tracking this information because if the
 * variables array grows >1M and has to be split into pieces we'd get stuck in
 * an infinite loop. Storing this information in memcache means it can be lost,
 * but in that case the pieces will still eventually be auto-expired by
 * memcache.
 *
 * @param string $cid
 *   The cid of the root multi-piece value.
 * @param integer $exp
 *   Timestamp when the cached item expires. If NULL, the $cid will be deleted.
 *
 * @return bool
 *   TRUE on succes, FALSE otherwise.
 */
function dmemcache_piece_cache_set($cid, $exp = NULL) {

  // Always refresh cached copy to minimize multi-thread race window.
  $piece_cache =& drupal_static('dmemcache_piece_cache', array());
  $piece_cache = dmemcache_get('__dmemcache_piece_cache');
  if (!is_array($piece_cache)) {
    $piece_cache = array();
  }
  if (isset($exp)) {
    if ($exp <= 0) {

      // If no expiration time is set, defaults to 30 days.
      $exp = REQUEST_TIME + 2592000;
    }
    $piece_cache[$cid] = $exp;
  }
  else {
    unset($piece_cache[$cid]);
  }
  return dmemcache_set('__dmemcache_piece_cache', $piece_cache);
}

/**
 * Determine if a key has multi-piece values.
 *
 *
 * @param string $cid
 *   The cid to check for multi-piece values.
 *
 * @return integer
 *   Expiration time if key has multi-piece values, otherwise FALSE.
 */
function dmemcache_piece_cache_get($name) {
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['piece_cache'] =& drupal_static('dmemcache_piece_cache', FALSE);
  }
  $piece_cache =& $drupal_static_fast['piece_cache'];
  if (!is_array($piece_cache)) {
    $piece_cache = dmemcache_get('__dmemcache_piece_cache');

    // On a website with no over-sized cache pieces, initialize the variable so
    // we never load it more than once per page versus once per DELETE.
    if (!is_array($piece_cache)) {
      dmemcache_set('__dmemcache_piece_cache', array());
    }
  }
  if (isset($piece_cache[$name])) {

    // Return the expiration time of the multi-piece cache item.
    return $piece_cache[$name];
  }

  // Item doesn't have multiple pieces.
  return FALSE;
}

/**
 * Collect statistics if enabled.
 *
 * Optimized function to determine whether or not we should be collecting
 * statistics. Also starts a timer to track how long individual memcache
 * operations take.
 *
 * @return bool
 *   TRUE or FALSE if statistics should be collected.
 */
function dmemcache_stats_init() {
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast =& drupal_static(__FUNCTION__, array(
      'variable_checked' => NULL,
      'user_access_checked' => NULL,
    ));
  }
  $variable_checked =& $drupal_static_fast['variable_checked'];
  $user_access_checked =& $drupal_static_fast['user_access_checked'];

  // Confirm DRUPAL_BOOTSTRAP_VARIABLES has been reached. We don't use
  // drupal_get_bootstrap_phase() as it's buggy. We can use variable_get() here
  // because _drupal_bootstrap_variables() includes module.inc immediately
  // after it calls variable_initialize().
  if (!isset($variable_checked) && function_exists('module_list')) {
    $variable_checked = variable_get('show_memcache_statistics', FALSE);
  }

  // If statistics are enabled we need to check user access.
  if (!empty($variable_checked) && !isset($user_access_checked) && !empty($GLOBALS['user']) && function_exists('user_access')) {

    // Statistics are enabled and the $user object has been populated, so check
    // that the user has access to view them.
    $user_access_checked = user_access('access memcache statistics');
  }

  // Return whether or not statistics are enabled and the user can access them.
  if ((!isset($variable_checked) || $variable_checked) && (!isset($user_access_checked) || $user_access_checked)) {
    timer_start('dmemcache');
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Save memcache statistics to be displayed at end of page generation.
 *
 * @param string $action
 *   The action being performed (get, set, etc...).
 * @param string $bin
 *   The memcache bin the action is being performed in.
 * @param array $keys
 *   Keyed array in the form (string)$cid => (bool)$success. The keys the
 *   action is being performed on, and whether or not it was a success.
 */
function dmemcache_stats_write($action, $bin, $keys) {
  global $_dmemcache_stats;

  // Determine how much time elapsed to execute this action.
  $time = timer_read('dmemcache');

  // Build the 'all' and 'ops' arrays displayed by memcache_admin.module.
  foreach ($keys as $key => $success) {
    $_dmemcache_stats['all'][] = array(
      number_format($time, 2),
      $action,
      $bin,
      $key,
      $success ? 'hit' : 'miss',
    );
    if (!isset($_dmemcache_stats['ops'][$action])) {
      $_dmemcache_stats['ops'][$action] = array(
        $action,
        0,
        0,
        0,
      );
    }
    $_dmemcache_stats['ops'][$action][1] += $time;
    if ($success) {
      $_dmemcache_stats['ops'][$action][2]++;
    }
    else {
      $_dmemcache_stats['ops'][$action][3]++;
    }
  }

  // Reset the dmemcache timer for timing the next memcache operation.
}

/**
 * Write to a debug log when enabled.
 *
 * @param string $action
 *   The action being performed (get, set, etc...).
 * @param string $bin
 *   The memcache bin the action is being performed in.
 * @param string $key
 *   The key on which the action is being performed on.
 * @param bool $rc
 *   The return code of the action performed.
 */
function _dmemcache_write_debug($action, $bin, $key, $rc) {
  static $memcache_debug_log = NULL;
  static $memcache_debug_verbose = NULL;
  if ($memcache_debug_log === NULL) {
    $memcache_debug_log = variable_get('memcache_debug_log', FALSE);
    $memcache_debug_verbose = variable_get('memcache_debug_verbose', FALSE);
  }
  if (($action == 'get' || $action == 'getMulti') && !$memcache_debug_verbose) {
    return;
  }
  elseif ($memcache_debug_log) {
    $debug_log = strtr(variable_get('memcache_debug_log_format', "!timestamp|!action|!bin|!cid|!rc\n"), array(
      '!timestamp' => date(variable_get('memcache_debug_time_format', 'U')),
      '!action' => $action,
      '!bin' => $bin,
      '!cid' => $key,
      '!rc' => (int) $rc,
    ));
    if (file_put_contents($memcache_debug_log, $debug_log, FILE_APPEND) === FALSE) {

      // This can lead to a lot of watchdog entries...
      register_shutdown_function('watchdog', 'memcache', 'Unable to write to debug log at !debug_file: !debug_log.', array(
        '!debug_file' => $memcache_debug_log,
        '!debug_log' => $debug_log,
      ), WATCHDOG_ERROR);
    }
  }
}

/**
 * Ensures memcache connection is authorized for the server
 * that is mapped for the given key.
 *
 * @param string $full_key
 *   The full key for the item whose server is checked for ascii
 *   authentication.
 * @param object $mc
 *   The memcache object.
 *
 * @return bool
 *   TRUE if the connection is authorized, FALSE otherwise.
 */
function _dmemcache_ensure_ascii_auth($full_key, $mc) {
  static $memcache_ascii_auth = NULL;
  static $memcache_ascii_auth_token = NULL;
  if (!isset($memcache_ascii_auth)) {
    $memcache_ascii_auth = _dmemcache_use_ascii_auth();
    $memcache_ascii_auth_token = variable_get('memcache_ascii_auth', FALSE);
  }

  // Immediately return if there is no memcache authentication.
  if (!$memcache_ascii_auth) {
    return FALSE;
  }

  // Login to the server.
  $rc = $mc
    ->setByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_KEY, $memcache_ascii_auth_token);
  if (!$rc && $mc
    ->getResultCode() == MEMCACHED_SERVER_TIMED_OUT) {

    // Quitting is the only way to recover from this failure.
    $mc
      ->quit();
    $rc = $mc
      ->setByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_KEY, $memcache_ascii_auth_token);
  }
  if (!$rc) {
    $code = $mc
      ->getResultCode();
    $message = $mc
      ->getResultMessage();
    $t = explode(' ', $memcache_ascii_auth_token);
    $s = $mc
      ->getServerByKey($full_key);
    register_shutdown_function('watchdog', 'memcache', 'Memcache ascii authentication failed to login with user %user to server: %server; error code: %code with message %message', array(
      '%server' => $s['host'] . ':' . $s['port'],
      '%user' => $t[0],
      '%code' => $code,
      '%message' => $message,
    ), WATCHDOG_ERROR);
    return FALSE;
  }

  // Login was successful, check if our lifetime key exists already.
  $rc = $mc
    ->getByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY);
  if ($rc) {
    return TRUE;
  }

  // Set the lifetime key.
  $mc
    ->setByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY, TRUE);

  // Confirm the lifetime key properly set, setByKey() can return success
  // even when it fails.
  return $mc
    ->getByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY);
}

/**
 * Performs ascii authentication to the server given by host and port.
 *
 * @param string $host
 *   The hostname of the server to authenticate to.
 * @param int $port
 *   The port of the server to authenticate to.
 * @param object $mc
 *   The memcache object.
 *
 * @return bool
 *   TRUE on success, FALSE otherwise.
 */
function _dmemcache_ascii_authenticate_server($host, $port, $mc) {
  $rc = FALSE;

  // Find a working key that matches the specified server.
  for ($key = 0; $key < 1000; $key++) {

    // It's impossible to directly address a server, but we need to
    // login to the specified connection before it can be used.
    // So we check all keys from 0 to 1000 if it would map to this server.
    // Once we have found one, we set the authentication data, because any key
    // will work for authentication as the authentication data includes user and
    // password.
    $s = $mc
      ->getServerByKey($key);
    if ($s['host'] == $host && $s['port'] == $port) {
      $rc = _dmemcache_ensure_ascii_auth($key, $mc);
      break;
    }
  }

  // This should never happen.
  if ($key == 1000) {
    register_shutdown_function('watchdog', 'memcache', 'Memcache ascii authentication could not find the server to authenticate to.', array(), WATCHDOG_ERROR);
    return FALSE;
  }
  return $rc;
}

/**
 * Checks if cache items are really missing or only missing ascii authentication.
 *
 * This is essentially a cache miss and hence we would be going into a heavy
 * code path anyway, therefore it is performance wise okay to check a special
 * key that exists on all memcache servers to see if this is:
 *
 * a) real cache miss (where we need to do all the hard work)
 * b) ascii authentication failure (where we just need to login and then can
 *    hopefully have a cache hit)
 *
 * The edge case of the special lifetime key not existing is treated as a real
 * cache miss.
 *
 * This is justified by the case that most likely memcache was just restarted
 * (else the lifetime key would not be missing) and hence the real cache request
 * likely would have been a real cache miss anyway.
 *
 * @param array $results
 *   The results array to check for a 2nd chance.
 * @param object $mc
 *   The memcache object.
 */
function _dmemcache_get_multi_2nd_chance_ascii_auth($results, $mc) {

  // Cache misses are very costly: Find out, if any servers need authentication.
  $servers = array();
  foreach ($results as $full_key => $value) {
    if (!empty($value)) {
      continue;
    }
    $s = $mc
      ->getServerByKey($full_key);
    $server_key = $s['host'] . ':' . $s['port'];
    $servers[$server_key][$full_key] = $full_key;
  }
  if (empty($servers)) {
    return array();
  }
  $try_again_full_keys = array();
  foreach ($servers as $server_key => $full_keys) {

    // Use the first key of the list as all keys map to this server.
    $full_key = current($full_keys);

    // Check if lifetime token exists and hence if we are authorized
    // on this server.
    $rc = $mc
      ->getByKey($full_key, DRUPAL_MEMCACHE_ASCII_AUTH_LIFETIME_KEY);
    if (empty($rc) && _dmemcache_ensure_ascii_auth($full_key, $mc)) {

      // If we logged in after first failing a fetch for our lifetime key,
      // then these keys need to be retried as the "not found" could have been
      // an authentication failure.
      $try_again_full_keys += $full_keys;
    }
  }

  // In case no authentication succeeded, there will be no keys to get.
  if (empty($try_again_full_keys)) {
    return array();
  }
  $new_results = $mc
    ->getMulti($try_again_full_keys);
  if (empty($new_results)) {
    return array();
  }
  return $new_results;
}

/**
 * If using ascii authentication, re-authenticates all servers.
 *
 * @param string $mc
 *   The mc object to re-authenticate all servers on.
 *
 * @return bool
 *   TRUE on success, FALSE on failure.
 */
function _dmemcache_reset_ascii_auth($mc) {
  static $memcache_ascii_auth = NULL;
  if (!isset($memcache_ascii_auth)) {
    $memcache_ascii_auth = _dmemcache_use_ascii_auth();
  }
  if (!$memcache_ascii_auth) {
    return FALSE;
  }

  // Need to check all servers for authentication.
  $servers = $mc
    ->getServerList();
  foreach ($servers as $s) {
    $rc = _dmemcache_ascii_authenticate_server($s['host'], $s['port'], $mc);
    if (!$rc) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Returns whether memcache_ascii_auth is used or not.
 *
 * @return bool
 *   TRUE if authentication is used, FALSE otherwise.
 */
function _dmemcache_use_ascii_auth() {
  static $memcache_ascii_auth = NULL;
  if (!isset($memcache_ascii_auth)) {
    $memcache_ascii_auth = variable_get('memcache_ascii_auth', FALSE);
    $extension = dmemcache_extension();
    if ($memcache_ascii_auth && $extension == 'Memcache') {
      register_shutdown_function('watchdog', 'memcache', 'Memcache ascii authentication can only be used with Memcached extension', array(), WATCHDOG_ERROR);
      $memcache_ascii_auth = FALSE;
    }
  }
  return (bool) $memcache_ascii_auth;
}

Functions

Namesort descending Description
dmemcache_add Add an item into memcache.
dmemcache_close Close the connection to the memcache instance.
dmemcache_connect Initiate a connection to memcache.
dmemcache_delete Deletes an item from the cache.
dmemcache_extension Determine which memcache extension to use: memcache or memcached.
dmemcache_flush Flush all stored items.
dmemcache_get Retrieve a value from the cache.
dmemcache_get_multi Retrieve multiple values from the cache.
dmemcache_instance Return a new memcache instance.
dmemcache_key Prefixes a key and ensures it is url safe.
dmemcache_object Return a Memcache object for the specified bin.
dmemcache_object_cluster Ensure that we're working with a proper cluster array.
dmemcache_piece_cache_get Determine if a key has multi-piece values.
dmemcache_piece_cache_set Track active keys with multi-piece values, necessary for efficient cleanup.
dmemcache_serialize Return proper serialize function.
dmemcache_serialize_extension Determine which serialize extension to use: serialize (none), igbinary, or msgpack.
dmemcache_set Place an item into memcache.
dmemcache_stats Retrieves statistics recorded during memcache operations.
dmemcache_stats_init Collect statistics if enabled.
dmemcache_stats_write Save memcache statistics to be displayed at end of page generation.
dmemcache_unserialize Return proper unserialize function.
_dmemcache_ascii_authenticate_server Performs ascii authentication to the server given by host and port.
_dmemcache_ensure_ascii_auth Ensures memcache connection is authorized for the server that is mapped for the given key.
_dmemcache_error_handler A temporary error handler which keeps track of the most recent error.
_dmemcache_get_multi_2nd_chance_ascii_auth Checks if cache items are really missing or only missing ascii authentication.
_dmemcache_get_pieces Retrieve a value from the cache.
_dmemcache_key_piece Generates a key name for a multi-part data piece based on the sequence ID.
_dmemcache_reset_ascii_auth If using ascii authentication, re-authenticates all servers.
_dmemcache_set_pieces Split a large item into pieces and place them into memcache
_dmemcache_use_ascii_auth Returns whether memcache_ascii_auth is used or not.
_dmemcache_write_debug Write to a debug log when enabled.

Constants

Globals

Namesort descending Description
$_dmemcache_stats