5050 * - Detects inactive waiting processes to prevent false-positives in concurrency throttling.
5151 */
5252
53+ use Cm \RedisSession \Handler \ClusterConfigInterface ;
5354use Cm \RedisSession \Handler \ConfigInterface ;
5455use Cm \RedisSession \Handler \ConfigSentinelPasswordInterface ;
5556use Cm \RedisSession \Handler \LoggerInterface ;
57+ use Cm \RedisSession \Handler \TlsOptionsConfigInterface ;
58+ use Cm \RedisSession \Handler \UsernameConfigInterface ;
5659
5760class Handler implements \SessionHandlerInterface
5861{
@@ -162,10 +165,20 @@ class Handler implements \SessionHandlerInterface
162165 const DEFAULT_LIFETIME = 60 ;
163166
164167 /**
165- * @var \Credis_Client
168+ * @var \Credis_Client|\Credis_Cluster
166169 */
167170 protected $ _redis ;
168171
172+ /**
173+ * @var bool
174+ */
175+ protected readonly bool $ _usePipeline ;
176+
177+ /**
178+ * @var bool
179+ */
180+ protected readonly bool $ _useCluster ;
181+
169182 /**
170183 * @var int
171184 */
@@ -278,10 +291,12 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
278291 $ host = $ this ->config ->getHost () ?: self ::DEFAULT_HOST ;
279292 $ port = $ this ->config ->getPort () ?: self ::DEFAULT_PORT ;
280293 $ pass = $ this ->config ->getPassword () ?: null ;
294+ $ username = $ this ->config instanceof UsernameConfigInterface ? $ this ->config ->getUsername () : null ;
281295 $ timeout = $ this ->config ->getTimeout () ?: self ::DEFAULT_TIMEOUT ;
282296 $ retries = $ this ->config ->getRetries () ?: self ::DEFAULT_RETRIES ;
283297 $ persistent = $ this ->config ->getPersistentIdentifier () ?: '' ;
284298 $ this ->_dbNum = $ this ->config ->getDatabase () ?: self ::DEFAULT_DATABASE ;
299+ $ tlsOptions = $ this ->config instanceof TlsOptionsConfigInterface ? $ this ->config ->getTlsOptions () : null ;
285300
286301 // General config
287302 $ this ->_readOnly = $ readOnly ;
@@ -307,6 +322,8 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
307322
308323 // Connect and authenticate
309324 if ($ sentinelServers && $ sentinelMaster ) {
325+ $ this ->_usePipeline = true ;
326+ $ this ->_useCluster = false ;
310327 $ servers = preg_split ('/\s*,\s*/ ' , trim ($ sentinelServers ), -1 , PREG_SPLIT_NO_EMPTY );
311328 $ sentinel = NULL ;
312329 $ exception = NULL ;
@@ -322,35 +339,35 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
322339 } catch (\CredisException $ e ) {
323340 // Prevent throwing exception if Sentinel has no password set (error messages are different between redis 5 and redis 6)
324341 if ($ e ->getCode () !== 0 || (
325- strpos ($ e ->getMessage (), 'ERR Client sent AUTH, but no password is set ' ) === false &&
342+ strpos ($ e ->getMessage (), 'ERR Client sent AUTH, but no password is set ' ) === false &&
326343 strpos ($ e ->getMessage (), 'ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct? ' ) === false )
327344 ) {
328345 throw $ e ;
329346 }
330347 }
331348 }
332-
349+
333350 $ sentinel = new \Credis_Sentinel ($ sentinelClient );
334351 $ sentinel
335352 ->setClientTimeout ($ timeout )
336353 ->setClientPersistent ($ persistent );
337354 $ redisMaster = $ sentinel ->getMasterClient ($ sentinelMaster );
338- if ($ pass ) $ redisMaster ->auth ($ pass );
355+ if ($ pass ) $ redisMaster ->auth ($ pass, $ username );
339356
340357 // Verify connected server is actually master as per Sentinel client spec
341358 if ($ sentinelVerifyMaster ) {
342359 $ roleData = $ redisMaster ->role ();
343360 if ( ! $ roleData || $ roleData [0 ] != 'master ' ) {
344361 usleep (100000 ); // Sleep 100ms and try again
345362 $ redisMaster = $ sentinel ->getMasterClient ($ sentinelMaster );
346- if ($ pass ) $ redisMaster ->auth ($ pass );
363+ if ($ pass ) $ redisMaster ->auth ($ pass, $ username );
347364 $ roleData = $ redisMaster ->role ();
348365 if ( ! $ roleData || $ roleData [0 ] != 'master ' ) {
349366 throw new \Exception ('Unable to determine master redis server. ' );
350367 }
351368 }
352369 }
353- if ($ this ->_dbNum || $ persistent ) $ redisMaster ->select (0 );
370+ if (( $ this ->_dbNum || $ persistent) && ! $ this -> _useCluster ) $ redisMaster ->select (0 );
354371
355372 $ this ->_redis = $ redisMaster ;
356373 break 2 ;
@@ -366,24 +383,61 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
366383 }
367384 }
368385 else {
369- $ this ->_redis = new \Credis_Client ($ host , $ port , $ timeout , $ persistent , 0 , $ pass );
386+ if (($ config instanceof ClusterConfigInterface) && ($ config ->isCluster ())) {
387+ $ this ->_redis = new \Credis_Cluster (
388+ $ config ->getClusterName (),
389+ $ config ->getClusterSeeds (),
390+ $ timeout ,
391+ 0 ,
392+ $ config ->getClusterUsePersistentConnection (),
393+ $ pass ,
394+ $ username ,
395+ $ tlsOptions ,
396+ );
397+ $ this ->_usePipeline = false ;
398+ $ this ->_useCluster = true ;
399+ } else {
400+ $ this ->_redis = new \Credis_Client (
401+ $ host ,
402+ $ port ,
403+ $ timeout ,
404+ $ persistent ,
405+ 0 ,
406+ $ pass ,
407+ $ username ,
408+ $ tlsOptions
409+ );
410+ $ this ->_usePipeline = true ;
411+ $ this ->_useCluster = false ;
412+ }
370413 $ this ->_redis ->setMaxConnectRetries ($ retries );
371414 if ($ this ->hasConnection () == false ) {
372415 throw new ConnectionFailedException ('Unable to connect to Redis ' );
373416 }
374417 }
375-
376418 // Destructor order cannot be predicted
377419 $ this ->_redis ->setCloseOnDestruct (false );
378- $ this ->_log (
379- sprintf (
380- "%s initialized for connection to %s:%s after %.5f seconds " ,
381- get_class ($ this ),
382- $ this ->_redis ->getHost (),
383- $ this ->_redis ->getPort (),
384- (microtime (true ) - $ timeStart )
385- )
386- );
420+ if ($ this ->_useCluster ) {
421+ $ this ->_log (
422+ sprintf (
423+ "%s initialized for connection to %s after %.5f seconds " ,
424+ get_class ($ this ),
425+ (!empty ($ this ->_redis ->getClusterSeeds ())) ?
426+ var_export ($ this ->_redis ->getClusterSeeds (), true ) : $ this ->_redis ->getClusterName (),
427+ (microtime (true ) - $ timeStart )
428+ )
429+ );
430+ } else {
431+ $ this ->_log (
432+ sprintf (
433+ "%s initialized for connection to %s:%s after %.5f seconds " ,
434+ get_class ($ this ),
435+ $ this ->_redis ->getHost (),
436+ $ this ->_redis ->getPort (),
437+ (microtime (true ) - $ timeStart )
438+ )
439+ );
440+ }
387441 }
388442
389443 /**
@@ -459,7 +513,7 @@ public function read($sessionId)
459513 $ timeStart = microtime (true );
460514 $ this ->_log (sprintf ("Attempting to take lock on ID %s " , $ sessionId ));
461515
462- $ this ->_redis ->select ($ this ->_dbNum );
516+ if (! $ this -> _useCluster ) $ this ->_redis ->select ($ this ->_dbNum );
463517 while ($ this ->_useLocking && !$ this ->_readOnly )
464518 {
465519 // Increment lock value for this session and retrieve the new value
@@ -639,18 +693,19 @@ public function read($sessionId)
639693 );
640694 }
641695 }
642-
643- // Set session data and expiration
644- $ this ->_redis ->pipeline ();
696+ if ($ this ->_usePipeline ) {
697+ // Set session data and expiration
698+ $ this ->_redis ->pipeline ();
699+ }
645700 if ( ! empty ($ setData )) {
646701 $ this ->_redis ->hMSet ($ sessionId , $ setData );
647702 }
648703 $ this ->_redis ->expire ($ sessionId , 3600 *6 ); // Expiration will be set to correct value when session is written
649- $ this ->_redis ->exec ();
650-
704+ if ($ this ->_usePipeline ) {
705+ $ this ->_redis ->exec ();
706+ }
651707 // Reset flag in case of multiple session read/write operations
652708 $ this ->_sessionWritten = false ;
653-
654709 return $ sessionData ? (string ) $ this ->_decodeData ($ sessionData ) : '' ;
655710 }
656711
@@ -673,7 +728,7 @@ public function write($sessionId, $sessionData)
673728
674729 // Do not overwrite the session if it is locked by another pid
675730 try {
676- if ($ this ->_dbNum ) $ this ->_redis ->select ($ this ->_dbNum ); // Prevent conflicts with other connections?
731+ if ($ this ->_dbNum && ! $ this -> _useCluster ) $ this ->_redis ->select ($ this ->_dbNum ); // Prevent conflicts with other connections?
677732
678733 if ( ! $ this ->_useLocking
679734 || ( ! ($ pid = $ this ->_redis ->hGet ('sess_ ' .$ sessionId , 'pid ' )) || $ pid == $ this ->_getPid ())
@@ -711,10 +766,14 @@ public function write($sessionId, $sessionData)
711766 public function destroy ($ sessionId )
712767 {
713768 $ this ->_log (sprintf ("Destroying ID %s " , $ sessionId ));
714- $ this ->_redis ->pipeline ();
715- if ($ this ->_dbNum ) $ this ->_redis ->select ($ this ->_dbNum );
769+ if ($ this ->_usePipeline ) {
770+ $ this ->_redis ->pipeline ();
771+ }
772+ if ($ this ->_dbNum && !$ this ->_useCluster ) $ this ->_redis ->select ($ this ->_dbNum );
716773 $ this ->_redis ->unlink (self ::SESSION_PREFIX .$ sessionId );
717- $ this ->_redis ->exec ();
774+ if ($ this ->_usePipeline ) {
775+ $ this ->_redis ->exec ();
776+ }
718777 return true ;
719778 }
720779
@@ -832,7 +891,7 @@ protected function _encodeData($data)
832891 case 'lz4 ' : $ data = lz4_compress ($ data ); $ prefix = ':l4: ' ; break ;
833892 case 'gzip ' : $ data = gzcompress ($ data , 1 ); break ;
834893 }
835- if ($ data ) {
894+ if ($ data ) {
836895 $ data = $ prefix .$ data ;
837896 $ this ->_log (
838897 sprintf (
@@ -880,15 +939,19 @@ protected function _decodeData($data)
880939 protected function _writeRawSession ($ id , $ data , $ lifetime )
881940 {
882941 $ sessionId = 'sess_ ' . $ id ;
883- $ this ->_redis ->pipeline ()
884- ->select ($ this ->_dbNum )
885- ->hMSet ($ sessionId , array (
942+ if ($ this ->_usePipeline ) {
943+ $ this ->_redis ->pipeline ();
944+ }
945+ if (!$ this ->_useCluster ) $ this ->_redis ->select ($ this ->_dbNum );
946+ $ this ->_redis ->hMSet ($ sessionId , array (
886947 'data ' => $ this ->_encodeData ($ data ),
887948 'lock ' => 0 , // 0 so that next lock attempt will get 1
888- ))
889- ->hIncrBy ($ sessionId , 'writes ' , 1 )
890- ->expire ($ sessionId , min ((int )$ lifetime , (int )$ this ->_maxLifetime ))
891- ->exec ();
949+ ));
950+ $ this ->_redis ->hIncrBy ($ sessionId , 'writes ' , 1 );
951+ $ this ->_redis ->expire ($ sessionId , min ((int )$ lifetime , (int )$ this ->_maxLifetime ));
952+ if ($ this ->_usePipeline ) {
953+ $ this ->_redis ->exec ();
954+ }
892955 }
893956
894957 /**
0 commit comments