PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/include/AMP/System/Cache/Memcache.php

https://github.com/radicaldesigns/amp
PHP | 242 lines | 184 code | 36 blank | 22 comment | 34 complexity | 872223f1c70773f234b53a636e27d94f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, AGPL-1.0
  1. <?php
  2. require_once( 'AMP/System/Cache/Cache.php');
  3. class AMP_System_Cache_Memcache extends AMP_System_Cache {
  4. var $_memcache_connection;
  5. function AMP_System_Cache_Memcache( ){
  6. $this->__construct( );
  7. }
  8. function __construct( ){
  9. //ensure memcache is set
  10. $connected = $this->_init_connection( );
  11. if ( !$connected ) {
  12. return;
  13. }
  14. $cache_version = $this->cache_version();
  15. $this->_unique_site_key = AMP_SYSTEM_UNIQUE_ID . "@$cache_version";
  16. }
  17. //empty. just here so as not to call _save_index from the parent.
  18. function __destroy( ) {
  19. }
  20. function _init_connection( ){
  21. if (!class_exists( 'Memcache' )) return false;
  22. $memcache_connection = new Memcache;
  23. $server_list = explode( ',', AMP_SYSTEM_MEMCACHE_SERVER );
  24. $primary_server = array_shift( $server_list );
  25. $result = $memcache_connection->pconnect( $primary_server, AMP_SYSTEM_MEMCACHE_PORT );
  26. if ( count( $server_list ) ) {
  27. foreach( $server_list as $additional_server ) {
  28. $result = ( $memcache_connection->addServer( $additional_server, AMP_SYSTEM_MEMCACHE_PORT ) || $result );
  29. }
  30. }
  31. if ( $result ) {
  32. $this->set_connection( $memcache_connection );
  33. } else {
  34. trigger_error( sprintf( AMP_TEXT_ERROR_CACHE_REQUEST_FAILED, 'Memcache', 'connect', 'Request: '.$_SERVER['REQUEST_URI'] ) );
  35. $result = $this->_restart_memcached();
  36. //try again
  37. if ( $result ) {
  38. $this->set_connection( $memcache_connection );
  39. }
  40. }
  41. return $result;
  42. }
  43. function set_connection( &$connection ) {
  44. $this->_memcache_connection = &$connection;
  45. }
  46. //necessary?
  47. function has_connection( ){
  48. return isset( $this->_memcache_connection );
  49. }
  50. function add( &$item, $key ){
  51. $authorized_key = $this->authorize( $key );
  52. if ( !$authorized_key ) return false;
  53. $result = $this->_memcache_connection->set( $authorized_key, $item, MEMCACHE_COMPRESSED );
  54. #timeout? : cache->set( $authorized_key, $item, MEMCACHE_COMPRESSED, AMP_SYSTEM_CACHE_TIMEOUT );
  55. if ( !$result ) {
  56. //try, try again
  57. $result = $this->_memcache_connection->set( $authorized_key, $item, MEMCACHE_COMPRESSED );
  58. $this->log_memcache_failure();
  59. }
  60. if ( !$result && AMP_DISPLAYMODE_DEBUG_CACHE ) {
  61. trigger_error( sprintf( AMP_TEXT_ERROR_CACHE_REQUEST_FAILED, get_class( $this ), __FUNCTION__, $key ) );
  62. }
  63. return $result;
  64. }
  65. function refresh( $key ) {
  66. return; //memcache no refreshy
  67. }
  68. function age( $key ) {
  69. return; //memcache no age
  70. }
  71. #XXX: unnecessary, delete me
  72. function contains( $key ){
  73. $authorized_key = $this->authorize( $key );
  74. if ( !$authorized_key ) return false;
  75. return $this->_memcache_connection->get( $authorized_key );
  76. }
  77. function &retrieve( $key ){
  78. $authorized_key = $this->authorize( $key );
  79. if ( !$authorized_key ) return false;
  80. $result = $this->_memcache_connection->get( $authorized_key );
  81. if ( !$result && AMP_DISPLAYMODE_DEBUG_CACHE ) {
  82. trigger_error( sprintf( AMP_TEXT_ERROR_CACHE_REQUEST_FAILED, get_class( $this ), __FUNCTION__, $key ));
  83. }
  84. return $result;
  85. }
  86. function delete( $key ){
  87. $authorized_key = $this->authorize( $key );
  88. if ( !$authorized_key ) return false;
  89. return $this->_memcache_connection->delete( $authorized_key );
  90. }
  91. # $key_token is ignored for now - no internal namespaces at this point
  92. function clear( $key_token = null ){
  93. $cache_version = $this->_memcache_connection->increment( $this->cache_version_key() );
  94. $this->_unique_site_key = AMP_SYSTEM_UNIQUE_ID . "@$cache_version";
  95. }
  96. function cache_version_key() {
  97. return AMP_SYSTEM_UNIQUE_ID.'_cache_version';
  98. }
  99. function cache_version() {
  100. $cache_version = $this->_memcache_connection->get( $this->cache_version_key() );
  101. if($cache_version===false) {
  102. $cache_version = rand(1, 10000);
  103. $this->_memcache_connection->set($this->cache_version_key(), $cache_version);
  104. }
  105. return $cache_version;
  106. }
  107. function shutdown( ){
  108. return $this->_memcache_connection->close( );
  109. }
  110. function failover( ){
  111. return 'file';
  112. }
  113. function _restart_memcached() {
  114. if(!defined('MEMCACHED_RC_SCRIPT')) define('MEMCACHED_RC_SCRIPT', '/usr/local/etc/rc.d/memcached.sh');
  115. if(!file_exists(MEMCACHED_RC_SCRIPT)) return false;
  116. $memcache_connection = new Memcache;
  117. //just try again
  118. $result = $memcache_connection->connect( AMP_SYSTEM_MEMCACHE_SERVER, AMP_SYSTEM_MEMCACHE_PORT );
  119. if ( $result ) {
  120. $this->set_connection( $memcache_connection );
  121. return true;
  122. }
  123. $lock = fopen("/tmp/restart-memcached.lock", "w");
  124. flock($lock, LOCK_EX|LOCK_NB, $currently_restarting);
  125. if ($currently_restarting) {
  126. //if $currently_restarting, then another process has the lock
  127. trigger_error(getmypid()." - could not get lock, hopefully someone else is restarting memcached");
  128. return false;
  129. } else {
  130. //we're the first one to try to restart
  131. trigger_error('connection to memcached failed, restarting');
  132. $stats = $memcache_connection->getStats();
  133. $pid = $stats['pid'];
  134. trigger_error(getmypid()." - acquired lock, restarting memcached with pid $pid");
  135. //use the rc.d script to force a restart
  136. $ret = exec(MEMCACHED_RC_SCRIPT.' forcerestart',$message,$code);
  137. if($code) {
  138. trigger_error("memcached restart script ".MEMCACHED_RC_SCRIPT." returned error code: $code");
  139. return false;
  140. }
  141. //give it 30 seconds to reconnect
  142. $start = 0;
  143. while(!($result = $memcache_connection->connect( AMP_SYSTEM_MEMCACHE_SERVER, AMP_SYSTEM_MEMCACHE_PORT ))) {
  144. if(++$start > 30) {
  145. break;
  146. }
  147. sleep(1);
  148. }
  149. if ( $result ) {
  150. $new_stats = $memcache_connection->getStats();
  151. $new_pid = $new_stats['pid'];
  152. trigger_error("memcached restarted successfully, new pid is $new_pid");
  153. } else {
  154. trigger_error('could not reestablish connection after 30 seconds');
  155. }
  156. //give up the lock
  157. flock($lock, LOCK_UN);
  158. mail('seth@radicaldesigns.org, austin@radicaldesigns.org', '[AMP] memcached restarted', "stats before restart:\n".print_r($stats, true), 'From: amp@radicaldesigns.org', '-fautomated@radicaldesigns.org');
  159. }
  160. return $result;
  161. }
  162. function log_memcache_failure() {
  163. //trigger_error('logging memcache failure');
  164. $log = '/tmp/amp-memcache-fails';
  165. $memcache_fail_limit = 100; // 100 failures in
  166. $memcache_fail_window = 60*5; // 5 minutes
  167. //try to create the file if it doesn't exist
  168. if($fh = file_exists($log) ? fopen($log, 'r+') : fopen($log, 'x+')) {
  169. if (flock($fh, LOCK_EX|LOCK_NB)) { // do an exclusive lock
  170. $AMP_MEMCACHE_FAILS = explode("\n",file_get_contents($log));
  171. $AMP_MEMCACHE_FAILS[] = time();
  172. if(count($AMP_MEMCACHE_FAILS) > $memcache_fail_limit) {
  173. //if there's more than the limit of failures, restart and reset the log
  174. //trigger_error( 'requested memcache restart');
  175. $this->_restart_memcached();
  176. $AMP_MEMCACHE_FAILS = array();
  177. } else {
  178. //only keep failures that have happened within the window
  179. $current_failures = $AMP_MEMCACHE_FAILS;
  180. $AMP_MEMCACHE_FAILS = array();
  181. foreach( $current_failures as $fail_time ) {
  182. if ( $fail_time < ( time( ) - $memcache_fail_window ) ) {
  183. continue;
  184. }
  185. $AMP_MEMCACHE_FAILS[] = $fail_time;
  186. }
  187. //trigger_error( 'returning ' . count( $AMP_MEMCACHE_FAILS ) . ' of ' . count( $current_failures ) . ' recent failures to the log');
  188. //$AMP_MEMCACHE_FAILS = array_filter($AMP_MEMCACHE_FAILS, create_function('$var', 'return $var > time() - '.$memcache_fail_window.';'));
  189. }
  190. //write out the log
  191. ftruncate($fh, 0);
  192. fwrite($fh, implode("\n",$AMP_MEMCACHE_FAILS));
  193. flock($fh, LOCK_UN); // release the lock
  194. } else {
  195. trigger_error("Couldn't lock memcache fail log at " . $log );
  196. }
  197. fclose($fh);
  198. } else {
  199. trigger_error('could not open memcache fail log at '.$log.' for reading and writing');
  200. }
  201. }
  202. }
  203. require_once( 'AMP/System/Cache/File.php');
  204. ?>