24use Psr\Log\LoggerAwareInterface;
25use Psr\Log\LoggerInterface;
79 if ( !class_exists(
'Redis' ) ) {
80 throw new RuntimeException(
81 __CLASS__ .
' requires a Redis client library. ' .
82 'See https://www.mediawiki.org/wiki/Redis#Setup' );
84 $this->logger = isset(
$options[
'logger'] )
86 : new \Psr\Log\NullLogger();
87 $this->connectTimeout =
$options[
'connectTimeout'];
88 $this->readTimeout =
$options[
'readTimeout'];
89 $this->persistent =
$options[
'persistent'];
90 $this->password =
$options[
'password'];
91 if ( !isset(
$options[
'serializer'] ) ||
$options[
'serializer'] ===
'php' ) {
92 $this->serializer = Redis::SERIALIZER_PHP;
93 } elseif (
$options[
'serializer'] ===
'igbinary' ) {
94 $this->serializer = Redis::SERIALIZER_IGBINARY;
95 } elseif (
$options[
'serializer'] ===
'none' ) {
96 $this->serializer = Redis::SERIALIZER_NONE;
98 throw new InvalidArgumentException(
"Invalid serializer specified." );
116 if ( !isset(
$options[
'connectTimeout'] ) ) {
119 if ( !isset(
$options[
'readTimeout'] ) ) {
122 if ( !isset(
$options[
'persistent'] ) ) {
125 if ( !isset(
$options[
'password'] ) ) {
153 if ( !isset( self::$instances[
$id] ) ) {
157 return self::$instances[
$id];
165 self::$instances = [];
182 if ( isset( $this->downServers[$server] ) ) {
184 if ( $now > $this->downServers[$server] ) {
186 unset( $this->downServers[$server] );
190 'Server "{redis_server}" is marked down for another ' .
191 ( $this->downServers[$server] - $now ) .
'seconds',
192 [
'redis_server' => $server ]
200 if ( isset( $this->connections[$server] ) ) {
201 foreach ( $this->connections[$server] as &$connection ) {
202 if ( $connection[
'free'] ) {
203 $connection[
'free'] =
false;
207 $this, $server, $connection[
'conn'],
$logger
214 throw new InvalidArgumentException(
215 __CLASS__ .
": invalid configured server \"$server\"" );
216 } elseif ( substr( $server, 0, 1 ) ===
'/' ) {
224 if ( preg_match(
'/^\[(.+)\]:(\d+)$/', $server, $m ) ) {
225 list( $host, $port ) = [ $m[1], (int)$m[2] ];
226 } elseif ( preg_match(
'/^([^:]+):(\d+)$/', $server, $m ) ) {
227 list( $host, $port ) = [ $m[1], (int)$m[2] ];
229 list( $host, $port ) = [ $server, 6379 ];
235 if ( $this->persistent ) {
236 $result = $conn->pconnect( $host, $port, $this->connectTimeout, $this->
id );
238 $result = $conn->connect( $host, $port, $this->connectTimeout );
242 'Could not connect to server "{redis_server}"',
243 [
'redis_server' => $server ]
250 if ( $this->password !==
null ) {
251 if ( !$conn->auth( $this->password ) ) {
253 'Authentication error connecting to "{redis_server}"',
254 [
'redis_server' => $server ]
258 }
catch ( RedisException
$e ) {
261 'Redis exception connecting to "{redis_server}"',
263 'redis_server' => $server,
272 $conn->setOption( Redis::OPT_READ_TIMEOUT, $this->readTimeout );
273 $conn->setOption( Redis::OPT_SERIALIZER, $this->serializer );
274 $this->connections[$server][] = [
'conn' => $conn,
'free' =>
false ];
292 foreach ( $this->connections[$server] as &$connection ) {
293 if ( $connection[
'conn'] === $conn && !$connection[
'free'] ) {
294 $connection[
'free'] =
true;
309 if ( $this->idlePoolSize <= count( $this->connections ) ) {
313 foreach ( $this->connections as &$serverConnections ) {
314 foreach ( $serverConnections as $key => &$connection ) {
315 if ( $connection[
'free'] ) {
316 unset( $serverConnections[$key] );
317 if ( --$this->idlePoolSize <= count( $this->connections ) ) {
335 $server = $cref->getServer();
336 $this->logger->error(
337 'Redis exception on server "{redis_server}"',
339 'redis_server' => $server,
343 foreach ( $this->connections[$server] as $key => $connection ) {
344 if ( $cref->isConnIdentical( $connection[
'conn'] ) ) {
345 $this->idlePoolSize -= $connection[
'free'] ? 1 : 0;
346 unset( $this->connections[$server][$key] );
369 if ( $this->password !==
null ) {
370 if ( !$conn->auth( $this->password ) ) {
371 $this->logger->error(
372 'Authentication error connecting to "{redis_server}"',
373 [
'redis_server' => $server ]
397 foreach ( $this->connections as $server => &$serverConnections ) {
398 foreach ( $serverConnections as $key => &$connection ) {
401 $conn = $connection[
'conn'];
403 }
catch ( RedisException
$e ) {
Helper class to handle automatically marking connectons as reusable (via RAII pattern)
Helper class to manage Redis connections.
static applyDefaultConfig(array $options)
string $connectTimeout
Connection timeout in seconds.
__construct(array $options, $id)
int $serializer
Serializer to use (Redis::SERIALIZER_*)
reauthenticateConnection( $server, Redis $conn)
Re-send an AUTH request to the redis server (useful after disconnects).
array $connections
(server name => ((connection info array),...)
getConnection( $server, LoggerInterface $logger=null)
Get a connection to a redis server.
__destruct()
Make sure connections are closed for sanity.
string $id
ID for persistent connections.
closeExcessIdleConections()
Close any extra idle connections if there are more than the limit.
handleError(RedisConnRef $cref, RedisException $e)
The redis extension throws an exception in response to various read, write and protocol errors.
static destroySingletons()
Destroy all singleton() instances.
freeConnection( $server, Redis $conn)
Mark a connection to a server as free to return to the pool.
static array $instances
(pool ID => RedisConnectionPool)
resetTimeout(Redis $conn, $timeout=null)
Adjust or reset the connection handle read timeout value.
string $password
Plaintext auth password.
int $idlePoolSize
Current idle pool size.
bool $persistent
Whether connections persist.
const SERVER_DOWN_TTL
integer; seconds to cache servers as "down".
array $downServers
(server name => UNIX timestamp)
string $readTimeout
Read timeout in seconds.
static singleton(array $options)
setLogger(LoggerInterface $logger)
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
processing should stop and the error should be shown to the user * false
returning false will NOT prevent logging $e