Skip to content
dmemcache.inc 41.5 KiB
Newer Older
/**
 * @file
 * A memcache API for Drupal.
 *
 * This file contains core dmemcache functions required by:
 *   memcache-lock.inc
 *   memcache-lock-code.inc
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.
 *   TRUE on succes, FALSE otherwise.
function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
  $full_key = dmemcache_key($key, $bin);
  if ($mc || ($mc = dmemcache_object($bin))) {
      $rc = $mc->set($full_key, $value, $exp);
      if (empty($rc)) {
        // 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);
        }
      }
      // 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);
/**
 * 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).
 *   (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.
 *   (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) {
  $full_key = dmemcache_key($key, $bin);
  if ($mc || ($mc = dmemcache_object($bin))) {
      $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);
/**
 * 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) {
  $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);
    // 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);
/**
 * 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.
  $pieces = dmemcache_get_multi($keys, $bin, $mc);
  if (count($pieces) != $item->piece_count) {
    // Some of the pieces don't exist, so our data cannot be reconstructed.
    return FALSE;
  }

  // Append all of the pieces together.
  $data = '';
  foreach ($pieces as $piece) {
    $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
 *   The item which was originally saved or FALSE
 */
function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) {
  $collect_stats = dmemcache_stats_init();
  $multi_stats = array();

  foreach ($keys as $key => $cid) {
    $full_key = dmemcache_key($cid, $bin);

    if ($collect_stats) {
      $multi_stats[$full_key] = FALSE;
    }
  $results = array();
  if ($mc || ($mc = dmemcache_object($bin))) {
      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);
      }
      $track_errors = ini_set('track_errors', '1');
      $php_errormsg = '';

      $results = @$mc->get($full_keys);

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

      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);
  // If $results is FALSE, convert it to an empty array.
  if (!$results) {
    $results = array();
  }
  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;

  if ($collect_stats) {
    dmemcache_stats_write('getMulti', $bin, $multi_stats);
  }

  _dmemcache_write_debug('getMulti', $bin, $full_key, !empty($results));

 * @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) {
  $full_keys = dmemcache_key($key, $bin, TRUE);
  if ($mc || ($mc = dmemcache_object($bin))) {
    foreach ($full_keys as $fk) {
      $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);
 * 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))) {
  }

  if ($collect_stats) {
    dmemcache_stats_write('flush', $bin, array('' => $rc));
  _dmemcache_write_debug('flush', $bin, $full_key, $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.
 */
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;

  foreach ($memcache_bins as $bin => $target) {
    if ($stats_bin == $bin) {
      if ($mc = dmemcache_object($bin)) {
        if ($mc instanceof Memcached) {
          $stats[$bin] = $mc->getStats();
        }
        // The PHP Memcache extension 3.x version throws an error if the stats
        // type is NULL or not in {reset, malloc, slabs, cachedump, items,
        // sizes}. If $stats_type is 'default', then no parameter should be
        // passed to the Memcache memcache_get_extended_stats() function.
        elseif ($mc instanceof Memcache) {
          if ($stats_type == 'default' || $stats_type == '') {
            $stats[$bin] = $mc->getExtendedStats();
          }
          // If $slab isn't zero, then we are dumping the contents of a
          // specific cache slab.
          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) {
    // Some variables don't logically aggregate.
    $no_aggregate = array(
      'pid',
      'time',
      'version',
      'pointer_size',
      'accepting_conns',
      'listen_disabled_num',
    );
    foreach ($stats as $bin => $servers) {
      if (is_array($servers)) {
        foreach ($servers as $server) {
          if (is_array($server)) {
            foreach ($server as $key => $value) {
              if (!in_array($key, $no_aggregate)) {
                if (isset($stats[$bin]['total'][$key])) {
                  $stats[$bin]['total'][$key] += $value;
                }
                else {
                  $stats[$bin]['total'][$key] = $value;
                }
              }
            }
          }
        }
      }
 * 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)) {
      $extension = ucfirst(strtolower($preferred));
    }
    // If no extension is set, default to Memcache.
    elseif (class_exists('Memcache')) {
      $extension = 'Memcache';
    }
    elseif (class_exists('Memcached')) {
      $extension = 'Memcached';
    }
/**
 * 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;
    }
    // Otherwise, find an available extension favoring configuration.
    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';
        }
        // Next try msgpack.
        else if ($msgpack_available) {
          $extension = 'msgpack';
        }
        // Finally fall back to core serialize.
        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';
  }
}

function dmemcache_instance($bin = 'cache') {
  static $error = FALSE;
  $extension = dmemcache_extension();
  if ($extension == 'Memcache') {
    if (variable_get('memcache_persistent', TRUE)) {
      $memcache = new Memcached($bin);
    }
    else {
      $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);
    }
    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
 * @param bool $connection
 *   TRUE or FALSE, whether the $memcache instance already has at least one
 *   open connection.
 * @return bool
 *   TRUE or FALSE if connection was successful.
 */
function dmemcache_connect($memcache, $server, $connection) {
  static $memcache_persistent = NULL;
  @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 ($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)) {
      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);
    }

    // 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_connect while connecting to !host:!port: !msg', array('!host' => $host, '!port' => $port, '!msg' => $php_errormsg), WATCHDOG_WARNING);
        $php_errormsg = '';
      }
      ini_set('track_errors', $track_errors);
    }
    else {
      $rc = $memcache->addServer($host, $port, $memcache_persistent);
    }
  }
  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)) {
      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 ($memcache_persistent) {
      $servers = $memcache->getServerList();
      $match = FALSE;
      foreach ($servers as $s) {
        if ($s['host'] == $host && $s['port'] == $port) {
          $match = TRUE;
          break;
        }
      }
      if (!$match) {
        $rc = $memcache->addServer($host, $port);
      }
      else {
        $rc = TRUE;
      }
    }
    else {
      $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;
  }
  elseif ($extension == 'Memcached' && $memcache instanceof Memcached) {
  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 $flush
 *   Defaults to FALSE. Rebuild the bin/server/cache mapping.
 * @return mixed
 *   A Memcache object, or FALSE on failure.
 */
function dmemcache_object($bin = NULL, $flush = FALSE) {
  static $memcache_cache = array();
  static $memcache_servers = array();
  static $memcache_bins = array();
  static $failed_connections = array();
    if (!empty($memcache_cache)) {
      foreach ($memcache_cache as $cluster) {
        if ($extension == 'Memcache' && $cluster instanceof Memcache) {
          $rc = @$cluster->close;
        }
        elseif ($extension == 'Memcached' && $cluster instanceof Memcached) {
          $rc = @$cluster->quit;
        }
        $memcache_cache = 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'];
      // Create a new memcache object for each cluster.
      // Track whether or not we've opened any memcache connections.
      $connection = FALSE;
      // 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;
            // 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;
        // 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];
/**
 * 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) {
  if ($prefixes = variable_get('memcache_key_prefix', '')) {
    if (is_array($prefixes)) {