Memcached.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Memcache;
  8. use OCP\HintException;
  9. use OCP\IMemcache;
  10. class Memcached extends Cache implements IMemcache {
  11. use CASTrait;
  12. /**
  13. * @var \Memcached $cache
  14. */
  15. private static $cache = null;
  16. use CADTrait;
  17. public function __construct($prefix = '') {
  18. parent::__construct($prefix);
  19. if (is_null(self::$cache)) {
  20. self::$cache = new \Memcached();
  21. $defaultOptions = [
  22. \Memcached::OPT_CONNECT_TIMEOUT => 50,
  23. \Memcached::OPT_RETRY_TIMEOUT => 50,
  24. \Memcached::OPT_SEND_TIMEOUT => 50,
  25. \Memcached::OPT_RECV_TIMEOUT => 50,
  26. \Memcached::OPT_POLL_TIMEOUT => 50,
  27. // Enable compression
  28. \Memcached::OPT_COMPRESSION => true,
  29. // Turn on consistent hashing
  30. \Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
  31. // Enable Binary Protocol
  32. \Memcached::OPT_BINARY_PROTOCOL => true,
  33. ];
  34. /**
  35. * By default enable igbinary serializer if available
  36. *
  37. * Psalm checks depend on if igbinary is installed or not with memcached
  38. * @psalm-suppress RedundantCondition
  39. * @psalm-suppress TypeDoesNotContainType
  40. */
  41. if (\Memcached::HAVE_IGBINARY) {
  42. $defaultOptions[\Memcached::OPT_SERIALIZER] =
  43. \Memcached::SERIALIZER_IGBINARY;
  44. }
  45. $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []);
  46. if (is_array($options)) {
  47. $options = $options + $defaultOptions;
  48. self::$cache->setOptions($options);
  49. } else {
  50. throw new HintException("Expected 'memcached_options' config to be an array, got $options");
  51. }
  52. $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers');
  53. if (!$servers) {
  54. $server = \OC::$server->getSystemConfig()->getValue('memcached_server');
  55. if ($server) {
  56. $servers = [$server];
  57. } else {
  58. $servers = [['localhost', 11211]];
  59. }
  60. }
  61. self::$cache->addServers($servers);
  62. }
  63. }
  64. /**
  65. * entries in XCache gets namespaced to prevent collisions between owncloud instances and users
  66. */
  67. protected function getNameSpace() {
  68. return $this->prefix;
  69. }
  70. public function get($key) {
  71. $result = self::$cache->get($this->getNameSpace() . $key);
  72. if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) {
  73. return null;
  74. } else {
  75. return $result;
  76. }
  77. }
  78. public function set($key, $value, $ttl = 0) {
  79. if ($ttl > 0) {
  80. $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl);
  81. } else {
  82. $result = self::$cache->set($this->getNameSpace() . $key, $value);
  83. }
  84. return $result || $this->isSuccess();
  85. }
  86. public function hasKey($key) {
  87. self::$cache->get($this->getNameSpace() . $key);
  88. return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
  89. }
  90. public function remove($key) {
  91. $result = self::$cache->delete($this->getNameSpace() . $key);
  92. return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND;
  93. }
  94. public function clear($prefix = '') {
  95. // Newer Memcached doesn't like getAllKeys(), flush everything
  96. self::$cache->flush();
  97. return true;
  98. }
  99. /**
  100. * Set a value in the cache if it's not already stored
  101. *
  102. * @param string $key
  103. * @param mixed $value
  104. * @param int $ttl Time To Live in seconds. Defaults to 60*60*24
  105. * @return bool
  106. */
  107. public function add($key, $value, $ttl = 0) {
  108. $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
  109. return $result || $this->isSuccess();
  110. }
  111. /**
  112. * Increase a stored number
  113. *
  114. * @param string $key
  115. * @param int $step
  116. * @return int | bool
  117. */
  118. public function inc($key, $step = 1) {
  119. $this->add($key, 0);
  120. $result = self::$cache->increment($this->getPrefix() . $key, $step);
  121. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  122. return false;
  123. }
  124. return $result;
  125. }
  126. /**
  127. * Decrease a stored number
  128. *
  129. * @param string $key
  130. * @param int $step
  131. * @return int | bool
  132. */
  133. public function dec($key, $step = 1) {
  134. $result = self::$cache->decrement($this->getPrefix() . $key, $step);
  135. if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
  136. return false;
  137. }
  138. return $result;
  139. }
  140. public static function isAvailable(): bool {
  141. return extension_loaded('memcached');
  142. }
  143. private function isSuccess(): bool {
  144. return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
  145. }
  146. }