MemcachePlugin.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. /**
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2009, StatusNet, Inc.
  5. *
  6. * Plugin to implement cache interface for memcache
  7. *
  8. * PHP version 5
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. * @category Cache
  24. * @package StatusNet
  25. * @author Evan Prodromou <evan@status.net>
  26. * @copyright 2009 StatusNet, Inc.
  27. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  28. * @link http://status.net/
  29. */
  30. if (!defined('STATUSNET')) {
  31. // This check helps protect against security problems;
  32. // your code file can't be executed directly from the web.
  33. exit(1);
  34. }
  35. /**
  36. * A plugin to use memcache for the cache interface
  37. *
  38. * This used to be encoded as config-variable options in the core code;
  39. * it's now broken out to a separate plugin. The same interface can be
  40. * implemented by other plugins.
  41. *
  42. * @category Cache
  43. * @package StatusNet
  44. * @author Evan Prodromou <evan@status.net>
  45. * @copyright 2009 StatusNet, Inc.
  46. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  47. * @link http://status.net/
  48. */
  49. class MemcachePlugin extends Plugin
  50. {
  51. const PLUGIN_VERSION = '2.0.0';
  52. static $cacheInitialized = false;
  53. private $_conn = null;
  54. public $servers = array('127.0.0.1;11211');
  55. public $compressThreshold = 20480;
  56. public $compressMinSaving = 0.2;
  57. public $persistent = null;
  58. public $defaultExpiry = 86400; // 24h
  59. /**
  60. * Initialize the plugin
  61. *
  62. * Note that onStartCacheGet() may have been called before this!
  63. *
  64. * @return boolean flag value
  65. */
  66. function onInitializePlugin()
  67. {
  68. if (self::$cacheInitialized) {
  69. $this->persistent = true;
  70. } else {
  71. // If we're a parent command-line process we need
  72. // to be able to close out the connection after
  73. // forking, so disable persistence.
  74. //
  75. // We'll turn it back on again the second time
  76. // through which will either be in a child process,
  77. // or a single-process script which is switching
  78. // configurations.
  79. $this->persistent = (php_sapi_name() == 'cli') ? false : true;
  80. }
  81. $this->_ensureConn();
  82. self::$cacheInitialized = true;
  83. return true;
  84. }
  85. /**
  86. * Get a value associated with a key
  87. *
  88. * The value should have been set previously.
  89. *
  90. * @param string &$key in; Lookup key
  91. * @param mixed &$value out; value associated with key
  92. *
  93. * @return boolean hook success
  94. */
  95. function onStartCacheGet(&$key, &$value)
  96. {
  97. $this->_ensureConn();
  98. $value = $this->_conn->get($key);
  99. Event::handle('EndCacheGet', array($key, &$value));
  100. return false;
  101. }
  102. /**
  103. * Associate a value with a key
  104. *
  105. * @param string &$key in; Key to use for lookups
  106. * @param mixed &$value in; Value to associate
  107. * @param integer &$flag in; Flag empty or Cache::COMPRESSED
  108. * @param integer &$expiry in; Expiry (passed through to Memcache)
  109. * @param boolean &$success out; Whether the set was successful
  110. *
  111. * @return boolean hook success
  112. */
  113. function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success)
  114. {
  115. $this->_ensureConn();
  116. if ($expiry === null) {
  117. $expiry = $this->defaultExpiry;
  118. }
  119. $success = $this->_conn->set($key, $value, $this->flag(intval($flag)), $expiry);
  120. Event::handle('EndCacheSet', array($key, $value, $flag,
  121. $expiry));
  122. return false;
  123. }
  124. /**
  125. * Atomically increment an existing numeric key value.
  126. * Existing expiration time will not be changed.
  127. *
  128. * @param string &$key in; Key to use for lookups
  129. * @param int &$step in; Amount to increment (default 1)
  130. * @param mixed &$value out; Incremented value, or false if key not set.
  131. *
  132. * @return boolean hook success
  133. */
  134. function onStartCacheIncrement(&$key, &$step, &$value)
  135. {
  136. $this->_ensureConn();
  137. $value = $this->_conn->increment($key, $step);
  138. Event::handle('EndCacheIncrement', array($key, $step, $value));
  139. return false;
  140. }
  141. /**
  142. * Delete a value associated with a key
  143. *
  144. * @param string &$key in; Key to lookup
  145. * @param boolean &$success out; whether it worked
  146. *
  147. * @return boolean hook success
  148. */
  149. function onStartCacheDelete(&$key, &$success)
  150. {
  151. $this->_ensureConn();
  152. $success = $this->_conn->delete($key);
  153. Event::handle('EndCacheDelete', array($key));
  154. return false;
  155. }
  156. function onStartCacheReconnect(&$success)
  157. {
  158. if (empty($this->_conn)) {
  159. // nothing to do
  160. return true;
  161. }
  162. if ($this->persistent) {
  163. common_log(LOG_ERR, "Cannot close persistent memcached connection");
  164. $success = false;
  165. } else {
  166. common_log(LOG_INFO, "Closing memcached connection");
  167. $success = $this->_conn->close();
  168. $this->_conn = null;
  169. }
  170. return false;
  171. }
  172. /**
  173. * Ensure that a connection exists
  174. *
  175. * Checks the instance $_conn variable and connects
  176. * if it is empty.
  177. *
  178. * @return void
  179. */
  180. private function _ensureConn()
  181. {
  182. if (empty($this->_conn)) {
  183. $this->_conn = new Memcache();
  184. if (is_array($this->servers)) {
  185. $servers = $this->servers;
  186. } else {
  187. $servers = array($this->servers);
  188. }
  189. foreach ($servers as $server) {
  190. if (strpos($server, ';') !== false) {
  191. list($host, $port) = explode(';', $server);
  192. } else {
  193. $host = $server;
  194. $port = 11211;
  195. }
  196. $this->_conn->addServer($host, $port, $this->persistent);
  197. }
  198. // Compress items stored in the cache if they're over threshold in size
  199. // (default 2KiB) and the compression would save more than min savings
  200. // ratio (default 0.2).
  201. // Allows the cache to store objects larger than 1MB (if they
  202. // compress to less than 1MB), and improves cache memory efficiency.
  203. $this->_conn->setCompressThreshold($this->compressThreshold,
  204. $this->compressMinSaving);
  205. }
  206. }
  207. /**
  208. * Translate general flags to Memcached-specific flags
  209. * @param int $flag
  210. * @return int
  211. */
  212. protected function flag($flag)
  213. {
  214. $out = 0;
  215. if ($flag & Cache::COMPRESSED == Cache::COMPRESSED) {
  216. $out |= MEMCACHE_COMPRESSED;
  217. }
  218. return $out;
  219. }
  220. function onPluginVersion(array &$versions)
  221. {
  222. $versions[] = array('name' => 'Memcache',
  223. 'version' => self::PLUGIN_VERSION,
  224. 'author' => 'Evan Prodromou, Craig Andrews',
  225. 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Memcache',
  226. 'rawdescription' =>
  227. // TRANS: Plugin description.
  228. _m('Use <a href="http://memcached.org/">Memcached</a> to cache query results.'));
  229. return true;
  230. }
  231. }