MemcachedPlugin.php 8.7 KB

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