Memcached.php 6.0 KB

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