You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Memcached.php 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Andreas Fischer <bantu@owncloud.com>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Robin Appelman <robin@icewind.nl>
  12. * @author Robin McCorkell <robin@mccorkell.me.uk>
  13. * @author Roeland Jago Douma <roeland@famdouma.nl>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Victor Dubiniuk <dubiniuk@owncloud.com>
  16. *
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OC\Memcache;
  33. use OCP\HintException;
  34. use OCP\IMemcache;
  35. class Memcached extends Cache implements IMemcache {
  36. use CASTrait;
  37. /**
  38. * @var \Memcached $cache
  39. */
  40. private static $cache = null;
  41. use CADTrait;
  42. public function __construct($prefix = '') {
  43. parent::__construct($prefix);
  44. if (is_null(self::$cache)) {
  45. self::$cache = new \Memcached();
  46. $defaultOptions = [
  47. \Memcached::OPT_CONNECT_TIMEOUT => 50,
  48. \Memcached::OPT_RETRY_TIMEOUT => 50,
  49. \Memcached::OPT_SEND_TIMEOUT => 50,
  50. \Memcached::OPT_RECV_TIMEOUT => 50,
  51. \Memcached::OPT_POLL_TIMEOUT => 50,
  52. // Enable compression
  53. \Memcached::OPT_COMPRESSION => true,
  54. // Turn on consistent hashing
  55. \Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
  56. // Enable Binary Protocol
  57. //\Memcached::OPT_BINARY_PROTOCOL => true,
  58. ];
  59. // by default enable igbinary serializer if available
  60. /** @psalm-suppress RedundantCondition */
  61. if (\Memcached::HAVE_IGBINARY) {
  62. $defaultOptions[\Memcached::OPT_SERIALIZER] =
  63. \Memcached::SERIALIZER_IGBINARY;
  64. }
  65. $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []);
  66. if (is_array($options)) {
  67. $options = $options + $defaultOptions;
  68. self::$cache->setOptions($options);
  69. } else {
  70. throw new HintException("Expected 'memcached_options' config to be an array, got $options");
  71. }
  72. $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers');
  73. if (!$servers) {
  74. $server = \OC::$server->getSystemConfig()->getValue('memcached_server');
  75. if ($server) {
  76. $servers = [$server];
  77. } else {
  78. $servers = [['localhost', 11211]];
  79. }
  80. }
  81. self::$cache->addServers($servers);
  82. }
  83. }
  84. /**
  85. * entries in XCache gets namespaced to prevent collisions between owncloud instances and users
  86. */
  87. protected function getNameSpace() {
  88. return $this->prefix;
  89. }
  90. public function get($key) {
  91. $result = self::$cache->get($this->getNameSpace() . $key);
  92. if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) {
  93. return null;
  94. } else {
  95. return $result;
  96. }
  97. }
  98. public function set($key, $value, $ttl = 0) {
  99. if ($ttl > 0) {
  100. $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl);
  101. } else {
  102. $result = self::$cache->set($this->getNameSpace() . $key, $value);
  103. }
  104. if ($result !== true) {
  105. $this->verifyReturnCode();
  106. }
  107. return $result;
  108. }
  109. public function hasKey($key) {
  110. self::$cache->get($this->getNameSpace() . $key);
  111. return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
  112. }
  113. public function remove($key) {
  114. $result = self::$cache->delete($this->getNameSpace() . $key);
  115. if (self::$cache->getResultCode() !== \Memcached::RES_NOTFOUND) {
  116. $this->verifyReturnCode();
  117. }
  118. return $result;
  119. }
  120. public function clear($prefix = '') {
  121. $prefix = $this->getNameSpace() . $prefix;
  122. $allKeys = self::$cache->getAllKeys();
  123. if ($allKeys === false) {
  124. // newer Memcached doesn't like getAllKeys(), flush everything
  125. self::$cache->flush();
  126. return true;
  127. }
  128. $keys = [];
  129. $prefixLength = strlen($prefix);
  130. foreach ($allKeys as $key) {
  131. if (substr($key, 0, $prefixLength) === $prefix) {
  132. $keys[] = $key;
  133. }
  134. }
  135. if (method_exists(self::$cache, 'deleteMulti')) {
  136. self::$cache->deleteMulti($keys);
  137. } else {
  138. foreach ($keys as $key) {
  139. self::$cache->delete($key);
  140. }
  141. }
  142. return true;
  143. }
  144. /**
  145. * Set a value in the cache if it's not already stored
  146. *
  147. * @param string $key
  148. * @param mixed $value
  149. * @param int $ttl Time To Live in seconds. Defaults to 60*60*24
  150. * @return bool
  151. * @throws \Exception
  152. */
  153. public function add($key, $value, $ttl = 0) {
  154. $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
  155. if (self::$cache->getResultCode() !== \Memcached::RES_NOTSTORED) {
  156. $this->verifyReturnCode();
  157. }
  158. return $result;
  159. }
  160. /**
  161. * Increase a stored number
  162. *
  163. * @param string $key
  164. * @param int $step
  165. * @return int | bool
  166. */
  167. public function inc($key, $step = 1) {
  168. $this->add($key, 0);
  169. $result = self::$cache->increment($this->getPrefix() . $key, $step);
  170. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  171. return false;
  172. }
  173. return $result;
  174. }
  175. /**
  176. * Decrease a stored number
  177. *
  178. * @param string $key
  179. * @param int $step
  180. * @return int | bool
  181. */
  182. public function dec($key, $step = 1) {
  183. $result = self::$cache->decrement($this->getPrefix() . $key, $step);
  184. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  185. return false;
  186. }
  187. return $result;
  188. }
  189. public static function isAvailable() {
  190. return extension_loaded('memcached');
  191. }
  192. /**
  193. * @throws \Exception
  194. */
  195. private function verifyReturnCode() {
  196. $code = self::$cache->getResultCode();
  197. if ($code === \Memcached::RES_SUCCESS) {
  198. return;
  199. }
  200. $message = self::$cache->getResultMessage();
  201. throw new \Exception("Error $code interacting with memcached : $message");
  202. }
  203. }