dmemcache.inc 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  1. <?php
  2. /**
  3. * @file
  4. * A memcache API for Drupal.
  5. *
  6. * This file contains core dmemcache functions required by:
  7. * memcache.inc
  8. * memcache-lock.inc
  9. * memcache-lock-code.inc
  10. */
  11. define('MEMCACHED_E2BIG', 37);
  12. global $_dmemcache_stats;
  13. $_dmemcache_stats = array('all' => array(), 'ops' => array());
  14. /**
  15. * Place an item into memcache.
  16. *
  17. * @param string $key
  18. * The string with which you will retrieve this item later.
  19. * @param mixed $value
  20. * The item to be stored.
  21. * @param int $exp
  22. * Parameter expire is expiration time in seconds. If it's 0, the item never
  23. * expires (but memcached server doesn't guarantee this item to be stored all
  24. * the time, it could be deleted from the cache to make place for other
  25. * items).
  26. * @param string $bin
  27. * The name of the Drupal subsystem that is making this call. Examples could
  28. * be 'cache', 'alias', 'taxonomy term' etc. It is possible to map different
  29. * $bin values to different memcache servers.
  30. * @param object $mc
  31. * Optionally pass in the memcache object. Normally this value is determined
  32. * automatically based on the bin the object is being stored to.
  33. *
  34. * @return bool
  35. * TRUE on succes, FALSE otherwise.
  36. */
  37. function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
  38. $collect_stats = dmemcache_stats_init();
  39. $full_key = dmemcache_key($key, $bin);
  40. $rc = FALSE;
  41. if ($mc || ($mc = dmemcache_object($bin))) {
  42. if ($mc instanceof Memcached) {
  43. $rc = $mc->set($full_key, $value, $exp);
  44. if (empty($rc)) {
  45. // If there was a MEMCACHED_E2BIG error, split the value into pieces
  46. // and cache them individually.
  47. if ($mc->getResultCode() == MEMCACHED_E2BIG) {
  48. $rc = _dmemcache_set_pieces($key, $value, $exp, $bin, $mc);
  49. }
  50. }
  51. }
  52. else {
  53. // The PECL Memcache library throws an E_NOTICE level error, which
  54. // $php_errormsg doesn't catch, so we need to log it ourselves.
  55. // Catch it with our own error handler.
  56. drupal_static_reset('_dmemcache_error_handler');
  57. set_error_handler('_dmemcache_error_handler');
  58. $rc = $mc->set($full_key, $value, MEMCACHE_COMPRESSED, $exp);
  59. // Restore the Drupal error handler.
  60. restore_error_handler();
  61. if (empty($rc)) {
  62. // If the object was too big, split the value into pieces and cache
  63. // them individually.
  64. $dmemcache_errormsg = &drupal_static('_dmemcache_error_handler');
  65. 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)) {
  66. $rc = _dmemcache_set_pieces($key, $value, $exp, $bin, $mc);
  67. }
  68. }
  69. }
  70. }
  71. if ($collect_stats) {
  72. dmemcache_stats_write('set', $bin, array($full_key => $rc));
  73. }
  74. _dmemcache_write_debug('set', $bin, $full_key, $rc);
  75. return $rc;
  76. }
  77. /**
  78. * A temporary error handler which keeps track of the most recent error.
  79. */
  80. function _dmemcache_error_handler($errno, $errstr) {
  81. $dmemcache_errormsg = &drupal_static(__FUNCTION__);
  82. $dmemcache_errormsg = $errstr;
  83. return TRUE;
  84. }
  85. /**
  86. * Split a large item into pieces and place them into memcache
  87. *
  88. * @param string $key
  89. * The string with which you will retrieve this item later.
  90. * @param mixed $value
  91. * The item to be stored.
  92. * @param int $exp
  93. * (optional) Expiration time in seconds. If it's 0, the item never expires
  94. * (but memcached server doesn't guarantee this item to be stored all the
  95. * time, it could be deleted from the cache to make place for other items).
  96. * @param string $bin
  97. * (optional) The name of the Drupal subsystem that is making this call.
  98. * Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible to
  99. * map different $bin values to different memcache servers.
  100. * @param object $mc
  101. * (optional) The memcache object. Normally this value is
  102. * determined automatically based on the bin the object is being stored to.
  103. *
  104. * @return bool
  105. */
  106. function _dmemcache_set_pieces($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
  107. static $recursion = 0;
  108. if (!empty($value->multi_part_data) || !empty($value->multi_part_pieces)) {
  109. // Prevent an infinite loop.
  110. return FALSE;
  111. }
  112. // Recursion happens when __dmemcache_piece_cache outgrows the largest
  113. // memcache slice (1 MiB by default) -- prevent an infinite loop and later
  114. // generate a watchdog error.
  115. if ($recursion) {
  116. return FALSE;
  117. }
  118. $recursion++;
  119. $full_key = dmemcache_key($key);
  120. // Cache the name of this key so if it is deleted later we know to also
  121. // delete the cache pieces.
  122. if (!dmemcache_piece_cache_set($full_key, $exp)) {
  123. // We're caching a LOT of large items. Our piece_cache has exceeded the
  124. // maximum memcache object size (default of 1 MiB).
  125. $piece_cache = &drupal_static('dmemcache_piece_cache', array());
  126. 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);
  127. }
  128. if (variable_get('memcache_log_data_pieces', 2)) {
  129. timer_start('memcache_split_data');
  130. }
  131. // We need to split the item into pieces, so convert it into a string.
  132. if (is_string($value)) {
  133. $data = $value;
  134. $serialized = FALSE;
  135. }
  136. else {
  137. $serialize_function = dmemcache_serialize();
  138. $data = $serialize_function($value);
  139. $serialized = TRUE;
  140. }
  141. // Account for any metadata stored alongside the data.
  142. $max_len = variable_get('memcache_data_max_length', 1048576) - (512 + strlen($full_key));
  143. $pieces = str_split($data, $max_len);
  144. $piece_count = count($pieces);
  145. // Create a placeholder item containing data about the pieces.
  146. $cache = new stdClass;
  147. // $key gets run through dmemcache_key() later inside dmemcache_set().
  148. $cache->cid = $key;
  149. $cache->created = REQUEST_TIME;
  150. $cache->expire = $exp;
  151. $cache->data = new stdClass;
  152. $cache->data->serialized = $serialized;
  153. $cache->data->piece_count = $piece_count;
  154. $cache->multi_part_data = TRUE;
  155. $result = dmemcache_set($cache->cid, $cache, $exp, $bin, $mc);
  156. // Create a cache item for each piece of data.
  157. foreach ($pieces as $id => $piece) {
  158. $cache = new stdClass;
  159. $cache->cid = _dmemcache_key_piece($key, $id);
  160. $cache->created = REQUEST_TIME;
  161. $cache->expire = $exp;
  162. $cache->data = $piece;
  163. $cache->multi_part_piece = TRUE;
  164. $result &= dmemcache_set($cache->cid, $cache, $exp, $bin, $mc);
  165. }
  166. if (variable_get('memcache_log_data_pieces', 2) && $piece_count >= variable_get('memcache_log_data_pieces', 2)) {
  167. if (function_exists('format_size')) {
  168. $data_size = format_size(strlen($data));
  169. }
  170. else {
  171. $data_size = number_format(strlen($data)) . ' byte';
  172. }
  173. 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);
  174. }
  175. $recursion--;
  176. // TRUE if all pieces were saved correctly.
  177. return $result;
  178. }
  179. /**
  180. * Add an item into memcache.
  181. *
  182. * @param string $key
  183. * The string with which you will retrieve this item later.
  184. * @param mixed $value
  185. * The item to be stored.
  186. * @param int $exp
  187. * (optional) Expiration time in seconds. If it's 0, the item never expires
  188. * (but memcached server doesn't guarantee this item to be stored all the
  189. * time, it could be deleted from the cache to make place for other items).
  190. * @param string $bin
  191. * (optional) The name of the Drupal subsystem that is making this call.
  192. * Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible
  193. * to map different $bin values to different memcache servers.
  194. * @param object $mc
  195. * (optional) The memcache object. Normally this value is
  196. * determined automatically based on the bin the object is being stored to.
  197. * @param bool $flag
  198. * (optional) If using the older memcache PECL extension as opposed to the
  199. * newer memcached PECL extension, the MEMCACHE_COMPRESSED flag can be set
  200. * to use zlib to store a compressed copy of the item. This flag option is
  201. * completely ignored when using the newer memcached PECL extension.
  202. *
  203. * @return bool
  204. * FALSE if placing the item into memcache failed.
  205. */
  206. function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag = FALSE) {
  207. $collect_stats = dmemcache_stats_init();
  208. $full_key = dmemcache_key($key, $bin);
  209. $rc = FALSE;
  210. if ($mc || ($mc = dmemcache_object($bin))) {
  211. if ($mc instanceof Memcached) {
  212. $rc = $mc->add($full_key, $value, $exp);
  213. }
  214. else {
  215. $rc = $mc->add($full_key, $value, $flag, $exp);
  216. }
  217. }
  218. if ($collect_stats) {
  219. dmemcache_stats_write('add', $bin, array($full_key => $rc));
  220. }
  221. _dmemcache_write_debug('add', $bin, $full_key, $rc);
  222. return $rc;
  223. }
  224. /**
  225. * Retrieve a value from the cache.
  226. *
  227. * @param string $key
  228. * The key with which the item was stored.
  229. * @param string $bin
  230. * The bin in which the item was stored.
  231. *
  232. * @return mixed
  233. * The item which was originally saved or FALSE
  234. */
  235. function dmemcache_get($key, $bin = 'cache', $mc = NULL) {
  236. $collect_stats = dmemcache_stats_init();
  237. $result = FALSE;
  238. $full_key = dmemcache_key($key, $bin);
  239. if ($mc || $mc = dmemcache_object($bin)) {
  240. $track_errors = ini_set('track_errors', '1');
  241. $php_errormsg = '';
  242. $result = @$mc->get($full_key);
  243. // This is a multi-part value.
  244. if (is_object($result) && !empty($result->multi_part_data)) {
  245. $result = _dmemcache_get_pieces($result->data, $result->cid, $bin, $mc);
  246. }
  247. if (!empty($php_errormsg)) {
  248. register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_get: !msg', array('!msg' => $php_errormsg), WATCHDOG_WARNING);
  249. $php_errormsg = '';
  250. }
  251. ini_set('track_errors', $track_errors);
  252. }
  253. if ($collect_stats) {
  254. dmemcache_stats_write('get', $bin, array($full_key => (bool) $result));
  255. }
  256. _dmemcache_write_debug('get', $bin, $full_key, (bool) $result);
  257. return $result;
  258. }
  259. /**
  260. * Retrieve a value from the cache.
  261. *
  262. * @param $item
  263. * The placeholder cache item from _dmemcache_set_pieces().
  264. * @param $key
  265. * The key with which the item was stored.
  266. * @param string $bin
  267. * (optional) The bin in which the item was stored.
  268. * @param object $mc
  269. * (optional) The memcache object. Normally this value is
  270. * determined automatically based on the bin the object is being stored to.
  271. *
  272. * @return object|bool
  273. * The item which was originally saved or FALSE.
  274. */
  275. function _dmemcache_get_pieces($item, $key, $bin = 'cache', $mc = NULL) {
  276. // Create a list of keys for the pieces of data.
  277. for ($id = 0; $id < $item->piece_count; $id++) {
  278. $keys[] = _dmemcache_key_piece($key, $id);
  279. }
  280. // Retrieve all the pieces of data.
  281. $pieces = dmemcache_get_multi($keys, $bin, $mc);
  282. if (count($pieces) != $item->piece_count) {
  283. // Some of the pieces don't exist, so our data cannot be reconstructed.
  284. return FALSE;
  285. }
  286. // Append all of the pieces together.
  287. $data = '';
  288. foreach ($pieces as $piece) {
  289. $data .= $piece->data;
  290. }
  291. unset($pieces);
  292. // If necessary unserialize the item.
  293. if (empty($item->serialized)) {
  294. return $data;
  295. }
  296. else {
  297. $unserialize_function = dmemcache_unserialize();
  298. return $unserialize_function($data);
  299. }
  300. }
  301. /**
  302. * Generates a key name for a multi-part data piece based on the sequence ID.
  303. *
  304. * @param int $id
  305. * The sequence ID of the data piece.
  306. * @param int $key
  307. * The original CID of the cache item.
  308. *
  309. * @return string
  310. */
  311. function _dmemcache_key_piece($key, $id) {
  312. return dmemcache_key('_multi'. (string)$id . "-$key");
  313. }
  314. /**
  315. * Retrieve multiple values from the cache.
  316. *
  317. * @param array $keys
  318. * The keys with which the items were stored.
  319. * @param string $bin
  320. * The bin in which the item was stored.
  321. *
  322. * @return mixed
  323. * The item which was originally saved or FALSE
  324. */
  325. function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) {
  326. $collect_stats = dmemcache_stats_init();
  327. $multi_stats = array();
  328. $full_keys = array();
  329. foreach ($keys as $key => $cid) {
  330. $full_key = dmemcache_key($cid, $bin);
  331. $full_keys[$cid] = $full_key;
  332. if ($collect_stats) {
  333. $multi_stats[$full_key] = FALSE;
  334. }
  335. }
  336. $results = array();
  337. if ($mc || ($mc = dmemcache_object($bin))) {
  338. if ($mc instanceof Memcached) {
  339. if (PHP_MAJOR_VERSION >= 7) {
  340. // See https://www.drupal.org/node/2728427
  341. $results = $mc->getMulti($full_keys, Memcached::GET_PRESERVE_ORDER);
  342. }
  343. else {
  344. $cas_tokens = NULL;
  345. $results = $mc->getMulti($full_keys, $cas_tokens, Memcached::GET_PRESERVE_ORDER);
  346. }
  347. }
  348. elseif ($mc instanceof Memcache) {
  349. $track_errors = ini_set('track_errors', '1');
  350. $php_errormsg = '';
  351. $results = @$mc->get($full_keys);
  352. if (!empty($php_errormsg)) {
  353. register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_get_multi: !msg', array('!msg' => $php_errormsg), WATCHDOG_WARNING);
  354. $php_errormsg = '';
  355. }
  356. ini_set('track_errors', $track_errors);
  357. // Order is not guaranteed, map responses to order of requests.
  358. if (is_array($results)) {
  359. $keys = array_fill_keys($full_keys, NULL);
  360. $results = array_merge($keys, $results);
  361. }
  362. }
  363. }
  364. // If $results is FALSE, convert it to an empty array.
  365. if (!$results) {
  366. $results = array();
  367. _dmemcache_write_debug('getMulti', $bin, implode(',', $full_keys), FALSE);
  368. }
  369. if ($collect_stats) {
  370. foreach ($multi_stats as $key => $value) {
  371. $multi_stats[$key] = isset($results[$key]) ? TRUE : FALSE;
  372. }
  373. }
  374. // Convert the full keys back to the cid.
  375. $cid_results = array();
  376. $cid_lookup = array_flip($full_keys);
  377. foreach ($results as $key => $value) {
  378. // This is a multi-part value.
  379. if (is_object($value) && !empty($value->multi_part_data)) {
  380. $value = _dmemcache_get_pieces($value->data, $value->cid, $bin, $mc);
  381. }
  382. $cid_results[$cid_lookup[$key]] = $value;
  383. _dmemcache_write_debug('getMulti', $bin, $key, TRUE);
  384. }
  385. if ($collect_stats) {
  386. dmemcache_stats_write('getMulti', $bin, $multi_stats);
  387. }
  388. return $cid_results;
  389. }
  390. /**
  391. * Deletes an item from the cache.
  392. *
  393. * @param string $key
  394. * The key with which the item was stored.
  395. * @param string $bin
  396. * The bin in which the item was stored.
  397. *
  398. * @return bool
  399. * Returns TRUE on success or FALSE on failure.
  400. */
  401. function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
  402. $collect_stats = dmemcache_stats_init();
  403. $full_keys = dmemcache_key($key, $bin, TRUE);
  404. $rc = FALSE;
  405. if ($mc || ($mc = dmemcache_object($bin))) {
  406. foreach ($full_keys as $fk) {
  407. $rc = $mc->delete($fk, 0);
  408. if ($rc) {
  409. // If the delete succeeded, we now check to see if this item has
  410. // multiple pieces also needing to be cleaned up. If the delete failed,
  411. // we assume these keys have already expired or been deleted (memcache
  412. // will auto-expire eventually if we're wrong).
  413. if ($piece_cache = dmemcache_piece_cache_get($fk)) {
  414. // First, remove from the piece_cache so we don't try and delete it
  415. // again in another thread, then delete the actual cache data pieces.
  416. dmemcache_piece_cache_set($fk, NULL);
  417. $next_id = 0;
  418. do {
  419. if ($inner_rc) {
  420. _dmemcache_write_debug("delete", $bin, $full_key, $inner_rc);
  421. }
  422. // Generate the cid of the next data piece.
  423. $piece_key = _dmemcache_key_piece($key, $next_id);
  424. $full_key = dmemcache_key($piece_key, $bin);
  425. $next_id++;
  426. // Keep deleting pieces until the operation fails. We accept that
  427. // this could lead to orphaned pieces as memcache will auto-expire
  428. // them eventually.
  429. } while ($inner_rc = $mc->delete($full_key, 0));
  430. _dmemcache_write_debug("delete", $bin, $full_key, $inner_rc);
  431. // Perform garbage collection for keys memcache has auto-expired. If
  432. // we don't do this, this variable could grow over enough time as a
  433. // slow memory leak.
  434. // @todo: Consider moving this to hook_cron() and requiring people to
  435. // enable the memcache module.
  436. timer_start('memcache_gc_piece_cache');
  437. $gc_counter = 0;
  438. $piece_cache = &drupal_static('dmemcache_piece_cache', array());
  439. foreach ($piece_cache as $cid => $expires) {
  440. if (REQUEST_TIME > $expires) {
  441. $gc_counter++;
  442. dmemcache_piece_cache_set($cid, NULL);
  443. }
  444. }
  445. if ($gc_counter) {
  446. 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);
  447. }
  448. }
  449. }
  450. if ($collect_stats) {
  451. dmemcache_stats_write('delete', $bin, array($fk => $rc));
  452. }
  453. _dmemcache_write_debug('delete', $bin, $fk, $rc);
  454. }
  455. }
  456. return $rc;
  457. }
  458. /**
  459. * Flush all stored items.
  460. *
  461. * Immediately invalidates all existing items. dmemcache_flush doesn't actually
  462. * free any resources, it only marks all the items as expired, so occupied
  463. * memory will be overwritten by new items.
  464. *
  465. * @param string $bin
  466. * The bin to flush. Note that this will flush all bins mapped to the same
  467. * server as $bin. There is no way at this time to empty just one bin.
  468. *
  469. * @return bool
  470. * Returns TRUE on success or FALSE on failure.
  471. */
  472. function dmemcache_flush($bin = 'cache', $mc = NULL) {
  473. $collect_stats = dmemcache_stats_init();
  474. $rc = FALSE;
  475. if ($mc || ($mc = dmemcache_object($bin))) {
  476. $rc = $mc->flush();
  477. }
  478. if ($collect_stats) {
  479. dmemcache_stats_write('flush', $bin, array('' => $rc));
  480. }
  481. _dmemcache_write_debug('flush', $bin, $full_key, $rc);
  482. return $rc;
  483. }
  484. /**
  485. * Retrieves statistics recorded during memcache operations.
  486. *
  487. * @param string $stats_bin
  488. * The bin to retrieve statistics for.
  489. * @param string $stats_type
  490. * The type of statistics to retrieve when using the Memcache extension.
  491. * @param bool $aggregate
  492. * Whether to aggregate statistics.
  493. */
  494. function dmemcache_stats($stats_bin = 'cache', $stats_type = 'default', $aggregate = FALSE) {
  495. $memcache_bins = variable_get('memcache_bins', array('cache' => 'default'));
  496. // The stats_type can be over-loaded with an integer slab id, if doing a
  497. // cachedump. We know we're doing a cachedump if $slab is non-zero.
  498. $slab = (int) $stats_type;
  499. $stats = array();
  500. foreach ($memcache_bins as $bin => $target) {
  501. if ($stats_bin == $bin) {
  502. if ($mc = dmemcache_object($bin)) {
  503. if ($mc instanceof Memcached) {
  504. $stats[$bin] = $mc->getStats();
  505. }
  506. // The PHP Memcache extension 3.x version throws an error if the stats
  507. // type is NULL or not in {reset, malloc, slabs, cachedump, items,
  508. // sizes}. If $stats_type is 'default', then no parameter should be
  509. // passed to the Memcache memcache_get_extended_stats() function.
  510. elseif ($mc instanceof Memcache) {
  511. if ($stats_type == 'default' || $stats_type == '') {
  512. $rc = $mc->getExtendedStats();
  513. if (is_array($rc)) {
  514. foreach ($rc as $server => $data) {
  515. if (empty($data)) {
  516. unset($rc[$server]);
  517. }
  518. }
  519. if (!empty($rc)) {
  520. $stats[$bin] = $rc;
  521. }
  522. }
  523. }
  524. // If $slab isn't zero, then we are dumping the contents of a
  525. // specific cache slab.
  526. elseif (!empty($slab)) {
  527. $stats[$bin] = $mc->getStats('cachedump', $slab);
  528. }
  529. else {
  530. $stats[$bin] = $mc->getExtendedStats($stats_type);
  531. }
  532. }
  533. }
  534. }
  535. }
  536. // Optionally calculate a sum-total for all servers in the current bin.
  537. if ($aggregate) {
  538. // Some variables don't logically aggregate.
  539. $no_aggregate = array(
  540. 'pid',
  541. 'time',
  542. 'version',
  543. 'pointer_size',
  544. 'accepting_conns',
  545. 'listen_disabled_num',
  546. );
  547. foreach ($stats as $bin => $servers) {
  548. if (is_array($servers)) {
  549. foreach ($servers as $server) {
  550. if (is_array($server)) {
  551. foreach ($server as $key => $value) {
  552. if (!in_array($key, $no_aggregate)) {
  553. if (isset($stats[$bin]['total'][$key])) {
  554. $stats[$bin]['total'][$key] += $value;
  555. }
  556. else {
  557. $stats[$bin]['total'][$key] = $value;
  558. }
  559. }
  560. }
  561. }
  562. }
  563. }
  564. }
  565. }
  566. return $stats;
  567. }
  568. /**
  569. * Determine which memcache extension to use: memcache or memcached.
  570. *
  571. * By default prefer the 'Memcache' PHP extension, though the default can be
  572. * overridden by setting memcache_extension in settings.php.
  573. */
  574. function dmemcache_extension() {
  575. static $extension = NULL;
  576. if ($extension === NULL) {
  577. // If an extension is specified in settings.php, use that when available.
  578. $preferred = variable_get('memcache_extension', NULL);
  579. if (isset($preferred) && class_exists($preferred, FALSE)) {
  580. $extension = ucfirst(strtolower($preferred));
  581. }
  582. // If no extension is set, default to Memcache.
  583. elseif (class_exists('Memcache', FALSE)) {
  584. $extension = 'Memcache';
  585. }
  586. elseif (class_exists('Memcached', FALSE)) {
  587. $extension = 'Memcached';
  588. }
  589. else {
  590. $extension = FALSE;
  591. }
  592. }
  593. return $extension;
  594. }
  595. /**
  596. * Determine which serialize extension to use: serialize (none), igbinary,
  597. * or msgpack.
  598. *
  599. * By default we prefer the igbinary extension, then the msgpack extension,
  600. * then the core serialize functions. This can be overridden in settings.php.
  601. */
  602. function dmemcache_serialize_extension() {
  603. static $extension = NULL;
  604. if ($extension === NULL) {
  605. $preferred = strtolower(variable_get('memcache_serialize', NULL));
  606. // Fastpath if we're forcing php's own serialize function.
  607. if ($preferred == 'serialize') {
  608. $extension = $preferred;
  609. }
  610. // Otherwise, find an available extension favoring configuration.
  611. else {
  612. $igbinary_available = extension_loaded('igbinary');
  613. $msgpack_available = extension_loaded('msgpack');
  614. if ($preferred == 'igbinary' && $igbinary_available) {
  615. $extension = $preferred;
  616. }
  617. elseif ($preferred == 'msgpack' && $msgpack_available) {
  618. $extension = $preferred;
  619. }
  620. else {
  621. // No (valid) serialize extension specified, try igbinary.
  622. if ($igbinary_available) {
  623. $extension = 'igbinary';
  624. }
  625. // Next try msgpack.
  626. else if ($msgpack_available) {
  627. $extension = 'msgpack';
  628. }
  629. // Finally fall back to core serialize.
  630. else {
  631. $extension = 'serialize';
  632. }
  633. }
  634. }
  635. }
  636. return $extension;
  637. }
  638. /**
  639. * Return proper serialize function.
  640. */
  641. function dmemcache_serialize() {
  642. switch (dmemcache_serialize_extension()) {
  643. case 'igbinary':
  644. return 'igbinary_serialize';
  645. case 'msgpack':
  646. return 'msgpack_pack';
  647. default:
  648. return 'serialize';
  649. }
  650. }
  651. /**
  652. * Return proper unserialize function.
  653. */
  654. function dmemcache_unserialize() {
  655. switch (dmemcache_serialize_extension()) {
  656. case 'igbinary':
  657. return 'igbinary_unserialize';
  658. case 'msgpack':
  659. return 'msgpack_unpack';
  660. default:
  661. return 'unserialize';
  662. }
  663. }
  664. /**
  665. * Return a new memcache instance.
  666. */
  667. function dmemcache_instance($bin = 'cache') {
  668. static $error = FALSE;
  669. $extension = dmemcache_extension();
  670. if ($extension == 'Memcache') {
  671. return new Memcache();
  672. }
  673. elseif ($extension == 'Memcached') {
  674. if (variable_get('memcache_persistent', TRUE)) {
  675. $memcache = new Memcached($bin);
  676. }
  677. else {
  678. $memcache = new Memcached;
  679. }
  680. $default_opts = array(
  681. Memcached::OPT_COMPRESSION => FALSE,
  682. Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
  683. );
  684. foreach ($default_opts as $key => $value) {
  685. $memcache->setOption($key, $value);
  686. }
  687. // See README.txt for setting custom Memcache options when using the
  688. // memcached PECL extension.
  689. $memconf = variable_get('memcache_options', array());
  690. foreach ($memconf as $key => $value) {
  691. $memcache->setOption($key, $value);
  692. }
  693. if (($sasl_username = variable_get('memcache_sasl_username', '')) && ($sasl_password = variable_get('memcache_sasl_password', ''))) {
  694. $memcache->setSaslAuthData($sasl_username, $sasl_password);
  695. }
  696. return $memcache;
  697. }
  698. else {
  699. if (!$error) {
  700. 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);
  701. $error = TRUE;
  702. }
  703. }
  704. return FALSE;
  705. }
  706. /**
  707. * Initiate a connection to memcache.
  708. *
  709. * @param object $memcache
  710. * A memcache instance obtained through dmemcache_instance.
  711. * @param string $server
  712. * A server string of the format "localhost:11211" or
  713. * "unix:///path/to/socket".
  714. * @param integer $weight
  715. * Weighted probability of talking to this server.
  716. *
  717. * @return bool
  718. * TRUE or FALSE if connection was successful.
  719. */
  720. function dmemcache_connect($memcache, $server, $weight) {
  721. static $memcache_persistent = NULL;
  722. $extension = dmemcache_extension();
  723. @list($host, $port) = explode(':', trim($server));
  724. if (empty($host)) {
  725. 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);
  726. }
  727. if (!isset($memcache_persistent)) {
  728. $memcache_persistent = variable_get('memcache_persistent', TRUE);
  729. }
  730. $port_error = FALSE;
  731. if ($extension == 'Memcache') {
  732. // Support unix sockets of the format 'unix:///path/to/socket'.
  733. if ($host == 'unix') {
  734. // Use full protocol and path as expected by Memcache extension.
  735. $host = $server;
  736. $port = 0;
  737. }
  738. else if (!isset($port)) {
  739. $port_error = TRUE;
  740. }
  741. }
  742. elseif ($extension == 'Memcached') {
  743. // Support unix sockets of the format 'unix:///path/to/socket'.
  744. if ($host == 'unix') {
  745. // Strip 'unix://' as expected by Memcached extension.
  746. $host = substr($server, 7);
  747. $port = 0;
  748. }
  749. else if (!isset($port)) {
  750. $port_error = TRUE;
  751. }
  752. }
  753. if ($port_error) {
  754. 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);
  755. }
  756. if ($extension == 'Memcache') {
  757. $rc = $memcache->addServer($host, $port, $memcache_persistent, $weight);
  758. }
  759. elseif ($extension == 'Memcached') {
  760. $match = FALSE;
  761. if ($memcache_persistent) {
  762. $servers = $memcache->getServerList();
  763. foreach ($servers as $s) {
  764. if ($s['host'] == $host && $s['port'] == $port) {
  765. $match = TRUE;
  766. break;
  767. }
  768. }
  769. }
  770. if (!$match) {
  771. $rc = $memcache->addServer($host, $port);
  772. }
  773. else {
  774. $rc = TRUE;
  775. }
  776. }
  777. else {
  778. $rc = FALSE;
  779. }
  780. return $rc;
  781. }
  782. /**
  783. * Close the connection to the memcache instance.
  784. */
  785. function dmemcache_close($memcache) {
  786. $extension = dmemcache_extension();
  787. if ($extension == 'Memcache' && $memcache instanceof Memcache) {
  788. $rc = @$memcache->close;
  789. }
  790. elseif ($extension == 'Memcached' && $memcache instanceof Memcached) {
  791. $rc = @$memcache->quit;
  792. }
  793. else {
  794. $rc = FALSE;
  795. }
  796. return $rc;
  797. }
  798. /**
  799. * Return a Memcache object for the specified bin.
  800. *
  801. * Note that there is nothing preventing developers from calling this function
  802. * directly to get the Memcache object. Do this if you need functionality not
  803. * provided by this API or if you need to use legacy code. Otherwise, use the
  804. * dmemcache (get, set, delete, flush) API functions provided here.
  805. *
  806. * @param string $bin
  807. * The bin which is to be used.
  808. * @param bool $flush
  809. * Defaults to FALSE. Rebuild the bin/server/cache mapping.
  810. *
  811. * @return mixed
  812. * A Memcache object, or FALSE on failure.
  813. */
  814. function dmemcache_object($bin = NULL, $flush = FALSE) {
  815. static $memcache_cache = array();
  816. static $memcache_servers = array();
  817. static $memcache_bins = array();
  818. static $failed_connections = array();
  819. if ($flush) {
  820. if (!empty($memcache_cache)) {
  821. foreach ($memcache_cache as $cluster) {
  822. if ($extension == 'Memcache' && $cluster instanceof Memcache) {
  823. $rc = @$cluster->close;
  824. }
  825. elseif ($extension == 'Memcached' && $cluster instanceof Memcached) {
  826. $rc = @$cluster->quit;
  827. }
  828. $memcache_cache = array();
  829. }
  830. }
  831. }
  832. $extension = dmemcache_extension();
  833. if (empty($memcache_cache) || empty($memcache_cache[$bin])) {
  834. if (empty($memcache_servers)) {
  835. // Load the variables from settings.php if set.
  836. $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
  837. $memcache_bins = variable_get('memcache_bins', array('cache' => 'default'));
  838. }
  839. // If not manually set, default this cluster to 'default'.
  840. $cluster = empty($memcache_bins[$bin]) ? 'default' : $memcache_bins[$bin];
  841. // If not manually set, map this bin to 'cache' which maps to the 'default'
  842. // cluster.
  843. if (empty($memcache_bins[$bin]) && !empty($memcache_cache['cache'])) {
  844. $memcache_cache[$bin] = &$memcache_cache['cache'];
  845. }
  846. else {
  847. // Create a new memcache object for each cluster.
  848. $memcache = dmemcache_instance($bin);
  849. // Track whether or not we've opened any memcache connections.
  850. $connection = FALSE;
  851. // Link all the servers to this cluster.
  852. foreach ($memcache_servers as $server => $b) {
  853. if ($c = dmemcache_object_cluster($b)) {
  854. if ($c['cluster'] == $cluster && !isset($failed_connections[$server])) {
  855. $rc = dmemcache_connect($memcache, $server, $c['weight'], $connection);
  856. if ($rc) {
  857. // We've made at least one connection.
  858. $connection = TRUE;
  859. }
  860. else {
  861. // Memcache connection failure. We can't log to watchdog directly
  862. // because we're in an early Drupal bootstrap phase where watchdog
  863. // is non-functional. Instead, register a shutdown handler so it
  864. // gets recorded at the end of the page load.
  865. register_shutdown_function('watchdog', 'memcache', 'Failed to connect to memcache server: !server', array('!server' => $server), WATCHDOG_ERROR);
  866. $failed_connections[$server] = FALSE;
  867. }
  868. }
  869. }
  870. }
  871. if ($connection) {
  872. // Map the current bin with the new Memcache object.
  873. $memcache_cache[$bin] = $memcache;
  874. // Now that all the servers have been mapped to this cluster, look for
  875. // other bins that belong to the cluster and map them too.
  876. foreach ($memcache_bins as $b => $c) {
  877. if ($c == $cluster && $b != $bin) {
  878. // Map this bin and cluster by reference.
  879. $memcache_cache[$b] = &$memcache_cache[$bin];
  880. }
  881. }
  882. }
  883. }
  884. }
  885. return empty($memcache_cache[$bin]) ? FALSE : $memcache_cache[$bin];
  886. }
  887. /**
  888. * Ensure that we're working with a proper cluster array.
  889. */
  890. function dmemcache_object_cluster($cluster) {
  891. if (!is_array($cluster)) {
  892. // Set defaults.
  893. $cluster = array(
  894. 'cluster' => $cluster,
  895. 'weight' => 1,
  896. );
  897. }
  898. if (!isset($cluster['cluster']) || !is_string($cluster['cluster'])) {
  899. // Cluster is required, complain if it's missing or invalid.
  900. register_shutdown_function('watchdog', 'memcache', 'Ignoring invalid or missing cluster definition, review your memcache_servers configuration.', array(), WATCHDOG_ERROR);
  901. return FALSE;
  902. }
  903. if (!isset($cluster['weight']) || !is_int($cluster['weight']) || $cluster['weight'] < 1) {
  904. // Weight is optional.
  905. $cluster['weight'] = 1;
  906. }
  907. return $cluster;
  908. }
  909. /**
  910. * Prefixes a key and ensures it is url safe.
  911. *
  912. * @param string $key
  913. * The key to prefix and encode.
  914. * @param string $bin
  915. * The cache bin which the key applies to.
  916. * @param string $multiple
  917. * If TRUE will return all possible prefix variations.
  918. *
  919. * @return string or array
  920. * The prefixed and encoded key(s).
  921. */
  922. function dmemcache_key($key, $bin = 'cache', $multiple = FALSE) {
  923. $prefix = '';
  924. if ($prefixes = variable_get('memcache_key_prefix', '')) {
  925. if (is_array($prefixes)) {
  926. // If no custom prefix defined for bin, use 'default'.
  927. if (empty($prefixes[$bin])) {
  928. $bin = 'default';
  929. }
  930. if (!empty($prefixes[$bin])) {
  931. // There can be multiple prefixes specified for each bin.
  932. if (is_array($prefixes[$bin])) {
  933. // Optionally return key with all prefixes.
  934. if ($multiple) {
  935. $prefix = array();
  936. foreach ($prefixes[$bin] as $pre) {
  937. $prefix[] = $pre . '-';
  938. }
  939. }
  940. // Otherwise just return a single prefixed key.
  941. else {
  942. $prefix = $prefixes[$bin][0] . '-';
  943. }
  944. }
  945. }
  946. }
  947. else {
  948. $prefix = $prefixes . '-';
  949. }
  950. }
  951. if (!is_array($prefix)) {
  952. $prefix = array($prefix);
  953. }
  954. $full_keys = array();
  955. foreach ($prefix as $p) {
  956. // When simpletest is running, emulate the simpletest database prefix here
  957. // to avoid the child site setting cache entries in the parent site.
  958. if (isset($GLOBALS['drupal_test_info']['test_run_id'])) {
  959. $p .= $GLOBALS['drupal_test_info']['test_run_id'];
  960. }
  961. $full_keys[] = urlencode($p . $bin . '-' . $key);
  962. }
  963. // Memcache truncates keys longer than 250 characters[*]. This could lead to
  964. // cache collisions, so we hash keys that are longer than this while still
  965. // retaining as much of the key bin and name as possible to aid in debugging.
  966. // The hashing algorithm used is configurable, with sha1 selected by default
  967. // as it performs quickly with minimal collisions. You can enforce shorter
  968. // keys by setting memcache_key_max_length in settings.php.
  969. // [*]https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L47
  970. $maxlen = variable_get('memcache_key_max_length', 250);
  971. foreach ($full_keys as $k => $full_key) {
  972. if (strlen($full_key) > $maxlen) {
  973. $full_keys[$k] = urlencode($prefix[$k] . $bin) . '-' . hash(variable_get('memcache_key_hash_algorithm', 'sha1'), $key);
  974. $full_keys[$k] .= '-' . substr(urlencode($key), 0, ($maxlen - 1) - strlen($full_keys[$k]) - 1);
  975. }
  976. }
  977. if ($multiple) {
  978. // An array of prefixed keys.
  979. return $full_keys;
  980. }
  981. else {
  982. // A single prefixed key.
  983. return array_shift($full_keys);
  984. }
  985. }
  986. /**
  987. * Track active keys with multi-piece values, necessary for efficient cleanup.
  988. *
  989. * We can't use variable_get/set for tracking this information because if the
  990. * variables array grows >1M and has to be split into pieces we'd get stuck in
  991. * an infinite loop. Storing this information in memcache means it can be lost,
  992. * but in that case the pieces will still eventually be auto-expired by
  993. * memcache.
  994. *
  995. * @param string $cid
  996. * The cid of the root multi-piece value.
  997. * @param integer $exp
  998. * Timestamp when the cached item expires. If NULL, the $cid will be deleted.
  999. *
  1000. * @return bool
  1001. * TRUE on succes, FALSE otherwise.
  1002. */
  1003. function dmemcache_piece_cache_set($cid, $exp = NULL) {
  1004. // Always refresh cached copy to minimize multi-thread race window.
  1005. $piece_cache = &drupal_static('dmemcache_piece_cache', array());
  1006. $piece_cache = dmemcache_get('__dmemcache_piece_cache');
  1007. if (isset($exp)) {
  1008. if ($exp <= 0) {
  1009. // If no expiration time is set, defaults to 30 days.
  1010. $exp = REQUEST_TIME + 2592000;
  1011. }
  1012. $piece_cache[$cid] = $exp;
  1013. }
  1014. else {
  1015. unset($piece_cache[$cid]);
  1016. }
  1017. return dmemcache_set('__dmemcache_piece_cache', $piece_cache);
  1018. }
  1019. /**
  1020. * Determine if a key has multi-piece values.
  1021. *
  1022. *
  1023. * @param string $cid
  1024. * The cid to check for multi-piece values.
  1025. *
  1026. * @return integer
  1027. * Expiration time if key has multi-piece values, otherwise FALSE.
  1028. */
  1029. function dmemcache_piece_cache_get($name) {
  1030. static $drupal_static_fast;
  1031. if (!isset($drupal_static_fast)) {
  1032. $drupal_static_fast['piece_cache'] = &drupal_static('dmemcache_piece_cache', FALSE);
  1033. }
  1034. $piece_cache = &$drupal_static_fast['piece_cache'];
  1035. if (!is_array($piece_cache)) {
  1036. $piece_cache = dmemcache_get('__dmemcache_piece_cache');
  1037. // On a website with no over-sized cache pieces, initialize the variable so
  1038. // we never load it more than once per page versus once per DELETE.
  1039. if (!is_array($piece_cache)) {
  1040. dmemcache_set('__dmemcache_piece_cache', array());
  1041. }
  1042. }
  1043. if (isset($piece_cache[$name])) {
  1044. // Return the expiration time of the multi-piece cache item.
  1045. return $piece_cache[$name];
  1046. }
  1047. // Item doesn't have multiple pieces.
  1048. return FALSE;
  1049. }
  1050. /**
  1051. * Collect statistics if enabled.
  1052. *
  1053. * Optimized function to determine whether or not we should be collecting
  1054. * statistics. Also starts a timer to track how long individual memcache
  1055. * operations take.
  1056. *
  1057. * @return bool
  1058. * TRUE or FALSE if statistics should be collected.
  1059. */
  1060. function dmemcache_stats_init() {
  1061. static $drupal_static_fast;
  1062. if (!isset($drupal_static_fast)) {
  1063. $drupal_static_fast = &drupal_static(__FUNCTION__, array('variable_checked' => NULL, 'user_access_checked' => NULL));
  1064. }
  1065. $variable_checked = &$drupal_static_fast['variable_checked'];
  1066. $user_access_checked = &$drupal_static_fast['user_access_checked'];
  1067. // Confirm DRUPAL_BOOTSTRAP_VARIABLES has been reached. We don't use
  1068. // drupal_get_bootstrap_phase() as it's buggy. We can use variable_get() here
  1069. // because _drupal_bootstrap_variables() includes module.inc immediately
  1070. // after it calls variable_initialize().
  1071. if (!isset($variable_checked) && function_exists('module_list')) {
  1072. $variable_checked = variable_get('show_memcache_statistics', FALSE);
  1073. }
  1074. // If statistics are enabled we need to check user access.
  1075. if (!empty($variable_checked) && !isset($user_access_checked) && !empty($GLOBALS['user']) && function_exists('user_access')) {
  1076. // Statistics are enabled and the $user object has been populated, so check
  1077. // that the user has access to view them.
  1078. $user_access_checked = user_access('access memcache statistics');
  1079. }
  1080. // Return whether or not statistics are enabled and the user can access them.
  1081. if ((!isset($variable_checked) || $variable_checked) && (!isset($user_access_checked) || $user_access_checked)) {
  1082. timer_start('dmemcache');
  1083. return TRUE;
  1084. }
  1085. else {
  1086. return FALSE;
  1087. }
  1088. }
  1089. /**
  1090. * Save memcache statistics to be displayed at end of page generation.
  1091. *
  1092. * @param string $action
  1093. * The action being performed (get, set, etc...).
  1094. * @param string $bin
  1095. * The memcache bin the action is being performed in.
  1096. * @param array $keys
  1097. * Keyed array in the form (string)$cid => (bool)$success. The keys the
  1098. * action is being performed on, and whether or not it was a success.
  1099. */
  1100. function dmemcache_stats_write($action, $bin, $keys) {
  1101. global $_dmemcache_stats;
  1102. // Determine how much time elapsed to execute this action.
  1103. $time = timer_read('dmemcache');
  1104. // Build the 'all' and 'ops' arrays displayed by memcache_admin.module.
  1105. foreach ($keys as $key => $success) {
  1106. $_dmemcache_stats['all'][] = array(
  1107. number_format($time, 2),
  1108. $action,
  1109. $bin,
  1110. $key,
  1111. $success ? 'hit' : 'miss',
  1112. );
  1113. if (!isset($_dmemcache_stats['ops'][$action])) {
  1114. $_dmemcache_stats['ops'][$action] = array($action, 0, 0, 0);
  1115. }
  1116. $_dmemcache_stats['ops'][$action][1] += $time;
  1117. if ($success) {
  1118. $_dmemcache_stats['ops'][$action][2]++;
  1119. }
  1120. else {
  1121. $_dmemcache_stats['ops'][$action][3]++;
  1122. }
  1123. }
  1124. // Reset the dmemcache timer for timing the next memcache operation.
  1125. }
  1126. /**
  1127. * Write to a debug log when enabled.
  1128. *
  1129. * @param string $action
  1130. * The action being performed (get, set, etc...).
  1131. * @param string $bin
  1132. * The memcache bin the action is being performed in.
  1133. * @param array $rc
  1134. * The return code of the action performed.
  1135. */
  1136. function _dmemcache_write_debug($action, $bin, $key, $rc) {
  1137. static $memcache_debug_log = NULL;
  1138. static $memcache_debug_verbose = NULL;
  1139. if ($memcache_debug_log === NULL) {
  1140. $memcache_debug_log = variable_get('memcache_debug_log', FALSE);
  1141. $memcache_debug_verbose = variable_get('memcache_debug_verbose', FALSE);
  1142. }
  1143. if (($action == 'get' || $action == 'getMulti') && !$memcache_debug_verbose) {
  1144. return;
  1145. }
  1146. else if ($memcache_debug_log) {
  1147. $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));
  1148. if (file_put_contents($memcache_debug_log, $debug_log, FILE_APPEND) === FALSE) {
  1149. // This can lead to a lot of watchdog entries...
  1150. 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);
  1151. }
  1152. }
  1153. }