| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262 |
- <?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);
- 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 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);
- }
- 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) {
- $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);
- // 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.
- $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();
- $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);
- }
- }
- 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();
- _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 ($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 ($collect_stats) {
- dmemcache_stats_write('flush', $bin, array('' => $rc));
- }
- _dmemcache_write_debug('flush', $bin, $full_key, $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.
- */
- 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();
- }
- // 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 == '') {
- $rc = $mc->getExtendedStats();
- if (is_array($rc)) {
- foreach ($rc as $server => $data) {
- if (empty($data)) {
- unset($rc[$server]);
- }
- }
- if (!empty($rc)) {
- $stats[$bin] = $rc;
- }
- }
- }
- // 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;
- }
- }
- }
- }
- }
- }
- }
- }
- 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));
- }
- // If no extension is set, default to Memcache.
- 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;
- }
- // 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';
- }
- }
- /**
- * 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_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
- * "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;
- $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);
- }
- $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);
- }
- 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 $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 ($flush) {
- 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();
- }
- }
- }
- $extension = dmemcache_extension();
- 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 . '-';
- }
- }
- // Otherwise just return a single prefixed key.
- 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 (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 array $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;
- }
- else if ($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_file, '!debug_log' => $debug_log), WATCHDOG_ERROR);
- }
- }
- }
|