24use Psr\Log\LoggerAwareInterface;
25use Psr\Log\LoggerInterface;
26use Psr\Log\NullLogger;
80 if ( !class_exists(
'Redis' ) ) {
81 throw new RuntimeException(
82 __CLASS__ .
' requires a Redis client library. ' .
83 'See https://www.mediawiki.org/wiki/Redis#Setup' );
85 $this->logger = $options[
'logger'] ??
new NullLogger();
86 $this->connectTimeout = $options[
'connectTimeout'];
87 $this->readTimeout = $options[
'readTimeout'];
88 $this->persistent = $options[
'persistent'];
89 $this->password = $options[
'password'];
90 if ( !isset( $options[
'serializer'] ) || $options[
'serializer'] ===
'php' ) {
91 $this->serializer = Redis::SERIALIZER_PHP;
92 } elseif ( $options[
'serializer'] ===
'igbinary' ) {
93 if ( !defined(
'Redis::SERIALIZER_IGBINARY' ) ) {
94 throw new InvalidArgumentException(
95 __CLASS__ .
': configured serializer "igbinary" not available' );
97 $this->serializer = Redis::SERIALIZER_IGBINARY;
98 } elseif ( $options[
'serializer'] ===
'none' ) {
99 $this->serializer = Redis::SERIALIZER_NONE;
101 throw new InvalidArgumentException(
"Invalid serializer specified." );
115 if ( !isset( $options[
'connectTimeout'] ) ) {
116 $options[
'connectTimeout'] = 1;
118 if ( !isset( $options[
'readTimeout'] ) ) {
119 $options[
'readTimeout'] = 1;
121 if ( !isset( $options[
'persistent'] ) ) {
122 $options[
'persistent'] =
false;
124 if ( !isset( $options[
'password'] ) ) {
125 $options[
'password'] =
null;
152 if ( !isset( self::$instances[
$id] ) ) {
153 self::$instances[
$id] =
new self( $options,
$id );
156 return self::$instances[
$id];
164 self::$instances = [];
184 if ( isset( $this->downServers[$server] ) ) {
186 if ( $now > $this->downServers[$server] ) {
188 unset( $this->downServers[$server] );
192 'Server "{redis_server}" is marked down for another ' .
193 ( $this->downServers[$server] - $now ) .
'seconds',
194 [
'redis_server' => $server ]
202 if ( isset( $this->connections[$server] ) ) {
203 foreach ( $this->connections[$server] as &$connection ) {
204 if ( $connection[
'free'] ) {
205 $connection[
'free'] =
false;
209 $this, $server, $connection[
'conn'],
$logger
216 throw new InvalidArgumentException(
217 __CLASS__ .
": invalid configured server \"$server\"" );
218 } elseif ( substr( $server, 0, 1 ) ===
'/' ) {
226 if ( preg_match(
'/^\[(.+)\]:(\d+)$/', $server, $m ) ) {
227 list( $host, $port ) = [ $m[1], (int)$m[2] ];
228 } elseif ( preg_match(
'/^([^:]+):(\d+)$/', $server, $m ) ) {
229 list( $host, $port ) = [ $m[1], (int)$m[2] ];
231 list( $host, $port ) = [ $server, 6379 ];
237 if ( $this->persistent ) {
238 $result = $conn->pconnect( $host, $port, $this->connectTimeout, $this->
id );
240 $result = $conn->connect( $host, $port, $this->connectTimeout );
244 'Could not connect to server "{redis_server}"',
245 [
'redis_server' => $server ]
252 if ( ( $this->password !==
null ) && !$conn->auth( $this->password ) ) {
254 'Authentication error connecting to "{redis_server}"',
255 [
'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 ) ) {
336 $this->logger->error(
337 'Redis exception on server "{redis_server}"',
339 'redis_server' => $server,
343 foreach ( $this->connections[$server] as $key => $connection ) {
345 $this->idlePoolSize -= $connection[
'free'] ? 1 : 0;
346 unset( $this->connections[$server][$key] );
369 if ( $this->password !==
null && !$conn->auth( $this->password ) ) {
370 $this->logger->error(
371 'Authentication error connecting to "{redis_server}"',
372 [
'redis_server' => $server ]
395 foreach ( $this->connections as $server => &$serverConnections ) {
396 foreach ( $serverConnections as $key => &$connection ) {
399 $conn = $connection[
'conn'];
401 }
catch ( RedisException $e ) {
Helper class to handle automatically marking connectons as reusable (via RAII pattern)
isConnIdentical(Redis $conn)
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)