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.

228 lines
6.0 KiB

3 years ago
  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 OC\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. if (\Memcached::HAVE_IGBINARY) {
  61. $defaultOptions[\Memcached::OPT_SERIALIZER] =
  62. \Memcached::SERIALIZER_IGBINARY;
  63. }
  64. $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []);
  65. if (is_array($options)) {
  66. $options = $options + $defaultOptions;
  67. self::$cache->setOptions($options);
  68. } else {
  69. throw new HintException("Expected 'memcached_options' config to be an array, got $options");
  70. }
  71. $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers');
  72. if (!$servers) {
  73. $server = \OC::$server->getSystemConfig()->getValue('memcached_server');
  74. if ($server) {
  75. $servers = [$server];
  76. } else {
  77. $servers = [['localhost', 11211]];
  78. }
  79. }
  80. self::$cache->addServers($servers);
  81. }
  82. }
  83. /**
  84. * entries in XCache gets namespaced to prevent collisions between owncloud instances and users
  85. */
  86. protected function getNameSpace() {
  87. return $this->prefix;
  88. }
  89. public function get($key) {
  90. $result = self::$cache->get($this->getNameSpace() . $key);
  91. if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) {
  92. return null;
  93. } else {
  94. return $result;
  95. }
  96. }
  97. public function set($key, $value, $ttl = 0) {
  98. if ($ttl > 0) {
  99. $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl);
  100. } else {
  101. $result = self::$cache->set($this->getNameSpace() . $key, $value);
  102. }
  103. if ($result !== true) {
  104. $this->verifyReturnCode();
  105. }
  106. return $result;
  107. }
  108. public function hasKey($key) {
  109. self::$cache->get($this->getNameSpace() . $key);
  110. return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
  111. }
  112. public function remove($key) {
  113. $result= self::$cache->delete($this->getNameSpace() . $key);
  114. if (self::$cache->getResultCode() !== \Memcached::RES_NOTFOUND) {
  115. $this->verifyReturnCode();
  116. }
  117. return $result;
  118. }
  119. public function clear($prefix = '') {
  120. $prefix = $this->getNameSpace() . $prefix;
  121. $allKeys = self::$cache->getAllKeys();
  122. if ($allKeys === false) {
  123. // newer Memcached doesn't like getAllKeys(), flush everything
  124. self::$cache->flush();
  125. return true;
  126. }
  127. $keys = [];
  128. $prefixLength = strlen($prefix);
  129. foreach ($allKeys as $key) {
  130. if (substr($key, 0, $prefixLength) === $prefix) {
  131. $keys[] = $key;
  132. }
  133. }
  134. if (method_exists(self::$cache, 'deleteMulti')) {
  135. self::$cache->deleteMulti($keys);
  136. } else {
  137. foreach ($keys as $key) {
  138. self::$cache->delete($key);
  139. }
  140. }
  141. return true;
  142. }
  143. /**
  144. * Set a value in the cache if it's not already stored
  145. *
  146. * @param string $key
  147. * @param mixed $value
  148. * @param int $ttl Time To Live in seconds. Defaults to 60*60*24
  149. * @return bool
  150. * @throws \Exception
  151. */
  152. public function add($key, $value, $ttl = 0) {
  153. $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
  154. if (self::$cache->getResultCode() !== \Memcached::RES_NOTSTORED) {
  155. $this->verifyReturnCode();
  156. }
  157. return $result;
  158. }
  159. /**
  160. * Increase a stored number
  161. *
  162. * @param string $key
  163. * @param int $step
  164. * @return int | bool
  165. */
  166. public function inc($key, $step = 1) {
  167. $this->add($key, 0);
  168. $result = self::$cache->increment($this->getPrefix() . $key, $step);
  169. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  170. return false;
  171. }
  172. return $result;
  173. }
  174. /**
  175. * Decrease a stored number
  176. *
  177. * @param string $key
  178. * @param int $step
  179. * @return int | bool
  180. */
  181. public function dec($key, $step = 1) {
  182. $result = self::$cache->decrement($this->getPrefix() . $key, $step);
  183. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  184. return false;
  185. }
  186. return $result;
  187. }
  188. public static function isAvailable() {
  189. return extension_loaded('memcached');
  190. }
  191. /**
  192. * @throws \Exception
  193. */
  194. private function verifyReturnCode() {
  195. $code = self::$cache->getResultCode();
  196. if ($code === \Memcached::RES_SUCCESS) {
  197. return;
  198. }
  199. $message = self::$cache->getResultMessage();
  200. throw new \Exception("Error $code interacting with memcached : $message");
  201. }
  202. }