From cc5b79a1cb132d2083d96f46107dceb68dfa364e Mon Sep 17 00:00:00 2001
From: Erick Hitter <ehitter@gmail.com>
Date: Mon, 3 Mar 2014 21:37:38 -0800
Subject: [PATCH] Improve behaviour when Redis is unavailable

* Catches connection exceptions rather than triggering fatals
* Forces all cache lookups to use the internal cache when Redis is unavailable
---
 object-cache.php | 76 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 58 insertions(+), 18 deletions(-)

diff --git a/object-cache.php b/object-cache.php
index 10152a1..364499c 100644
--- a/object-cache.php
+++ b/object-cache.php
@@ -190,14 +190,21 @@ class WP_Object_Cache {
 	 *
 	 * @var Predis\Client
 	 */
-	public $redis;
+	private $redis;
+
+	/**
+	 * Track if Redis is available
+	 *
+	 * @var bool
+	 */
+	private $redis_connected = false;
 
 	/**
 	 * Holds the non-Redis objects.
 	 *
 	 * @var array
 	 */
-	public $cache = array();
+	private $cache = array();
 
 	/**
 	 * List of global groups.
@@ -269,18 +276,33 @@ class WP_Object_Cache {
 
 		// Use Redis PECL library if available, otherwise default to bundled Predis library
 		if ( class_exists( 'Redis' ) ) {
-			$this->redis = new Redis();
-			$this->redis->connect( $redis['host'], $redis['port'] );
+			try {
+				$this->redis = new Redis();
+				$this->redis->connect( $redis['host'], $redis['port'] );
+
+				if ( isset( $redis['database'] ) ) {
+					$this->redis->select( $redis['database'] );
+				}
 
-			if ( isset( $redis['database'] ) ) {
-				$this->redis->select( $redis['database'] );
+				$this->redis_connected = true;
+			} catch ( RedisException $e ) {
+				$this->redis_connected = false;
 			}
 		} else {
-			require_once 'predis/autoload.php';
-			$this->redis = new Predis\Client( $redis );
+			try {
+				require_once 'predis/autoload.php';
+				$this->redis = new Predis\Client( $redis );
+
+				$this->redis_connected = true;
+			} catch ( Predis\Connection\ConnectionException $e ) {
+				$this->redis_connected = false;
+			}
 		}
 
-		unset( $redis );
+		// When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
+		if ( ! $this->redis_connected ) {
+			$this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $this->global_groups ) );
+		}
 
 		/**
 		 * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for
@@ -295,6 +317,15 @@ class WP_Object_Cache {
 		$this->blog_prefix   = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
 	}
 
+	/**
+	 * Is Redis available?
+	 *
+	 * @return bool
+	 */
+	protected function can_redis() {
+		return $this->redis_connected;
+	}
+
 	/**
 	 * Adds a value to cache.
 	 *
@@ -343,7 +374,7 @@ class WP_Object_Cache {
 		$derived_key = $this->build_key( $key, $group );
 
 		// If group is a non-Redis group, save to internal cache, not Redis
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 
 			// Check if conditions are right to continue
 			if (
@@ -387,12 +418,14 @@ class WP_Object_Cache {
 	public function delete( $key, $group = 'default' ) {
 		$derived_key = $this->build_key( $key, $group );
 
-		// Remove from no_mc_groups array
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		// Remove from no_redis_groups array
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 			if ( isset( $this->cache[$derived_key] ) ) {
 				unset( $this->cache[$derived_key] );
 
 				return true;
+			} else {
+				return false;
 			}
 		}
 
@@ -416,7 +449,10 @@ class WP_Object_Cache {
 		}
 
 		$this->cache = array();
-		$result = $this->parse_predis_response( $this->redis->flushdb() );
+
+		if ( $this->can_redis() ) {
+			$result = $this->parse_predis_response( $this->redis->flushdb() );
+		}
 
 		return $result;
 	}
@@ -433,7 +469,7 @@ class WP_Object_Cache {
 	public function get( $key, $group = 'default' ) {
 		$derived_key = $this->build_key( $key, $group );
 
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 			if ( isset( $this->cache[$derived_key] ) ) {
 				$this->cache_hits++;
 				return is_object( $this->cache[$derived_key] ) ? clone $this->cache[$derived_key] : $this->cache[$derived_key];
@@ -471,7 +507,7 @@ class WP_Object_Cache {
 		$derived_key = $this->build_key( $key, $group );
 
 		// If group is a non-Redis group, save to internal cache, not Redis
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 			$this->add_to_internal_cache( $derived_key, $value );
 
 			return true;
@@ -501,7 +537,7 @@ class WP_Object_Cache {
 		$offset = (int) $offset;
 
 		// If group is a non-Redis group, save to internal cache, not Redis
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 			$value = $this->get_from_internal_cache( $derived_key );
 			$value += $offset;
 			$this->add_to_internal_cache( $derived_key, $value );
@@ -530,7 +566,7 @@ class WP_Object_Cache {
 		$offset = (int) $offset;
 
 		// If group is a non-Redis group, save to internal cache, not Redis
-		if ( in_array( $group, $this->no_redis_groups ) ) {
+		if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
 			$value = $this->get_from_internal_cache( $derived_key );
 			$value -= $offset;
 			$this->add_to_internal_cache( $derived_key, $value );
@@ -688,7 +724,11 @@ class WP_Object_Cache {
 	public function add_global_groups( $groups ) {
 		$groups = (array) $groups;
 
-		$this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
+		if ( $this->can_redis() ) {
+			$this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
+		} else {
+			$this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
+		}
 	}
 
 	/**
-- 
GitLab