RedisCachePlugin.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Plugin implementing Redis based caching
  18. *
  19. * @category Files
  20. * @package GNUsocial
  21. * @author Stéphane Bérubé <chimo@chromic.org>
  22. * @author Miguel Dantas <biodantas@gmail.com>
  23. * @copyright 2018, 2019 Free Software Foundation, Inc http://www.fsf.org
  24. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  25. */
  26. defined('GNUSOCIAL') || die();
  27. use Predis\Client;
  28. use Predis\PredisException;
  29. class RedisCachePlugin extends Plugin
  30. {
  31. const PLUGIN_VERSION = '0.1.0';
  32. public static $cacheInitialized = false;
  33. public $persistent = null;
  34. // settings which can be set in config.php with addPlugin('Embed', ['param'=>'value', ...]);
  35. public $server = null;
  36. public $defaultExpiry = 86400; // 24h
  37. protected $client = null;
  38. public function onInitializePlugin()
  39. {
  40. if (self::$cacheInitialized) {
  41. $this->persistent = true;
  42. } else {
  43. // If we're a parent command-line process we need
  44. // to be able to close out the connection after
  45. // forking, so disable persistence.
  46. //
  47. // We'll turn it back on again the second time
  48. // through which will either be in a child process,
  49. // or a single-process script which is switching
  50. // configurations.
  51. $this->persistent = (php_sapi_name() === 'cli') ? false : true;
  52. }
  53. $this->ensureConn();
  54. self::$cacheInitialized = true;
  55. return true;
  56. }
  57. public function onStartCacheGet($key, &$value)
  58. {
  59. try {
  60. $this->ensureConn();
  61. $data = $this->client->get($key);
  62. } catch (PredisException $e) {
  63. common_log(LOG_ERR, 'RedisCache encountered exception ' . get_class($e) . ': ' . $e->getMessage());
  64. return true;
  65. }
  66. if (is_null($data)) {
  67. // Miss, let GS do its thing
  68. return true;
  69. }
  70. $ret = unserialize($data);
  71. if ($ret === false && $data !== 'b:0;') {
  72. common_log(LOG_ERR, 'RedisCache could not handle: ' . $data);
  73. return true;
  74. }
  75. // Hit, overwrite "value" and return false
  76. // to indicate we took care of this
  77. $value = $ret;
  78. return false;
  79. }
  80. public function onStartCacheSet($key, $value, $flag, $expiry, &$success)
  81. {
  82. $success = false;
  83. if (is_null($expiry)) {
  84. $expiry = $this->defaultExpiry;
  85. }
  86. try {
  87. $this->ensureConn();
  88. $ret = $this->client->setex($key, $expiry, serialize($value));
  89. } catch (PredisException $e) {
  90. $ret = false;
  91. common_log(LOG_ERR, 'RedisCache encountered exception ' . get_class($e) . ': ' . $e->getMessage());
  92. }
  93. if (is_bool($ret)
  94. || is_numeric($ret)) {
  95. $success = ($ret ? true : false);
  96. } elseif (is_object($ret) && method_exists($ret, 'getPayload')) {
  97. $success = ($ret->getPayload() === 'OK');
  98. }
  99. return !$success;
  100. }
  101. public function onStartCacheDelete($key)
  102. {
  103. if ($key === null) {
  104. return true;
  105. }
  106. try {
  107. $this->ensureConn();
  108. $ret = $this->client->del($key);
  109. } catch (PredisException $e) {
  110. common_log(LOG_ERR, 'RedisCache encountered exception ' . get_class($e) . ': ' . $e->getMessage());
  111. }
  112. // Let other caches delete stuff if we didn't succeed
  113. return isset($ret) && $ret === 1;
  114. }
  115. public function onStartCacheIncrement($key, $step, $value)
  116. {
  117. try {
  118. $this->ensureConn();
  119. $this->client->incrby($key, $step);
  120. } catch (PredisException $e) {
  121. common_log(LOG_ERR, 'RedisCache encountered exception ' . get_class($e) . ': ' . $e->getMessage());
  122. return true;
  123. }
  124. return false;
  125. }
  126. public function onStartCacheReconnect(bool &$success): bool
  127. {
  128. if (is_null($this->client)) {
  129. // nothing to do
  130. return true;
  131. }
  132. if ($this->persistent) {
  133. common_log(LOG_ERR, 'Cannot close persistent Redis connection');
  134. $success = false;
  135. } else {
  136. common_log(LOG_INFO, 'Closing Redis connection');
  137. $success = $this->client->disconnect();
  138. $this->client = null;
  139. }
  140. return false;
  141. }
  142. private function ensureConn(): void
  143. {
  144. if (is_null($this->client)) {
  145. $this->client = new Client(
  146. $this->server,
  147. ['persistent' => $this->persistent]
  148. );
  149. }
  150. }
  151. public function onPluginVersion(array &$versions): bool
  152. {
  153. $versions[] = [
  154. 'name' => 'RedisCache',
  155. 'version' => self::PLUGIN_VERSION,
  156. 'author' => 'Stéphane Bérubé (chimo)',
  157. 'homepage' => 'https://github.com/chimo/gs-rediscache',
  158. 'description' =>
  159. // TRANS: Plugin description.
  160. _m('Plugin implementing Redis as a backend for GNU social caching')
  161. ];
  162. return true;
  163. }
  164. }