Merge "Update docs for files in includes/actions/"
[lhc/web/wiklou.git] / includes / clientpool / RedisConnectionPool.php
index ef71b18..6cf7376 100644 (file)
@@ -283,6 +283,32 @@ class RedisConnectionPool {
                        }
                }
        }
+
+       /**
+        * Re-send an AUTH request to the redis server (useful after disconnects).
+        *
+        * This works around an upstream bug in phpredis. phpredis hides disconnects by transparently
+        * reconnecting, but it neglects to re-authenticate the new connection. To the user of the
+        * phpredis client API this manifests as a seemingly random tendency of connections to lose
+        * their authentication status.
+        *
+        * This method is for internal use only.
+        *
+        * @see https://github.com/nicolasff/phpredis/issues/403
+        *
+        * @param string $server
+        * @param Redis $conn
+        * @return bool Success
+        */
+       public function reauthenticateConnection( $server, Redis $conn ) {
+               if ( $this->password !== null ) {
+                       if ( !$conn->auth( $this->password ) ) {
+                               wfDebugLog( 'redis', "Authentication error connecting to $server" );
+                               return false;
+                       }
+               }
+               return true;
+       }
 }
 
 /**
@@ -298,6 +324,7 @@ class RedisConnRef {
        protected $conn;
 
        protected $server; // string
+       protected $lastError; // string
 
        /**
         * @param $pool RedisConnectionPool
@@ -310,8 +337,29 @@ class RedisConnRef {
                $this->conn = $conn;
        }
 
+       public function getLastError() {
+               return $this->lastError;
+       }
+
+       public function clearLastError() {
+               $this->lastError = null;
+       }
+
        public function __call( $name, $arguments ) {
-               return call_user_func_array( array( $this->conn, $name ), $arguments );
+               $conn = $this->conn; // convenience
+
+               $conn->clearLastError();
+               $res = call_user_func_array( array( $conn, $name ), $arguments );
+               if ( preg_match( '/^ERR operation not permitted\b/', $conn->getLastError() ) ) {
+                       $this->pool->reauthenticateConnection( $this->server, $conn );
+                       $conn->clearLastError();
+                       $res = call_user_func_array( array( $conn, $name ), $arguments );
+                       wfDebugLog( 'redis', "Used automatic re-authentication for method '$name'." );
+               }
+
+               $this->lastError = $conn->getLastError() ?: $this->lastError;
+
+               return $res;
        }
 
        /**
@@ -324,10 +372,21 @@ class RedisConnRef {
        public function luaEval( $script, array $params, $numKeys ) {
                $sha1 = sha1( $script ); // 40 char hex
                $conn = $this->conn; // convenience
+               $server = $this->server; // convenience
 
                // Try to run the server-side cached copy of the script
                $conn->clearLastError();
                $res = $conn->evalSha( $sha1, $params, $numKeys );
+               // If we got a permission error reply that means that (a) we are not in
+               // multi()/pipeline() and (b) some connection problem likely occured. If
+               // the password the client gave was just wrong, an exception should have
+               // been thrown back in getConnection() previously.
+               if ( preg_match( '/^ERR operation not permitted\b/', $conn->getLastError() ) ) {
+                       $this->pool->reauthenticateConnection( $server, $conn );
+                       $conn->clearLastError();
+                       $res = $conn->eval( $script, $params, $numKeys );
+                       wfDebugLog( 'redis', "Used automatic re-authentication for Lua script $sha1." );
+               }
                // If the script is not in cache, use eval() to retry and cache it
                if ( preg_match( '/^NOSCRIPT/', $conn->getLastError() ) ) {
                        $conn->clearLastError();
@@ -336,9 +395,11 @@ class RedisConnRef {
                }
 
                if ( $conn->getLastError() ) { // script bug?
-                       wfDebugLog( 'redis', "Lua script error: " . $conn->getLastError() );
+                       wfDebugLog( 'redis', "Lua script error on server $server: " . $conn->getLastError() );
                }
 
+               $this->lastError = $conn->getLastError() ?: $this->lastError;
+
                return $res;
        }