Add numerous missing @throws to method documentation
[lhc/web/wiklou.git] / includes / objectcache / SqlBagOStuff.php
index 209975b..222d475 100644 (file)
@@ -27,7 +27,6 @@
  * @ingroup Cache
  */
 class SqlBagOStuff extends BagOStuff {
-
        /**
         * @var LoadBalancer
         */
@@ -43,6 +42,9 @@ class SqlBagOStuff extends BagOStuff {
        var $shards = 1;
        var $tableName = 'objectcache';
 
+       protected $connFailureTime = 0; // UNIX timestamp
+       protected $connFailureError; // exception
+
        /**
         * Constructor. Parameters are:
         *   - server:   A server info structure in the format required by each
@@ -84,10 +86,17 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
+        * @throws
         * @return DatabaseBase
         */
        protected function getDB() {
                global $wgDebugDBTransactions;
+
+               # Don't keep timing out trying to connect for each call if the DB is down
+               if ( $this->connFailureError && ( time() - $this->connFailureTime ) < 60 ) {
+                       throw $this->connFailureError;
+               }
+
                if ( !isset( $this->db ) ) {
                        # If server connection info was given, use that
                        if ( $this->serverInfo ) {
@@ -107,7 +116,7 @@ class SqlBagOStuff extends BagOStuff {
                                if ( wfGetDB( DB_MASTER )->getType() == 'mysql' ) {
                                        $this->lb = wfGetLBFactory()->newMainLB();
                                        $this->db = $this->lb->getConnection( DB_MASTER );
-                                       $this->db->clearFlag( DBO_TRX );
+                                       $this->db->clearFlag( DBO_TRX ); // auto-commit mode
                                } else {
                                        $this->db = wfGetDB( DB_MASTER );
                                }
@@ -122,6 +131,7 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Get the table name for a given key
+        * @param $key string
         * @return string
         */
        protected function getTableByKey( $key ) {
@@ -135,6 +145,7 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Get the table name for a given shard index
+        * @param $index int
         * @return string
         */
        protected function getTableByShard( $index ) {
@@ -147,85 +158,103 @@ class SqlBagOStuff extends BagOStuff {
                }
        }
 
+       /**
+        * @param $key string
+        * @return mixed
+        */
        public function get( $key ) {
                $values = $this->getMulti( array( $key ) );
-               return $values[$key];
+               return array_key_exists( $key, $values ) ? $values[$key] : false;
        }
 
+       /**
+        * @param $keys array
+        * @return Array
+        */
        public function getMulti( array $keys ) {
                $values = array(); // array of (key => value)
 
-               $keysByTableName = array();
-               foreach ( $keys as $key ) {
-                       $tableName = $this->getTableByKey( $key );
-                       if ( !isset( $keysByTableName[$tableName] ) ) {
-                               $keysByTableName[$tableName] = array();
+               try {
+                       $db = $this->getDB();
+                       $keysByTableName = array();
+                       foreach ( $keys as $key ) {
+                               $tableName = $this->getTableByKey( $key );
+                               if ( !isset( $keysByTableName[$tableName] ) ) {
+                                       $keysByTableName[$tableName] = array();
+                               }
+                               $keysByTableName[$tableName][] = $key;
                        }
-                       $keysByTableName[$tableName][] = $key;
-               }
 
-               $db = $this->getDB();
-               $this->garbageCollect(); // expire old entries if any
+                       $this->garbageCollect(); // expire old entries if any
 
-               $dataRows = array();
-               foreach ( $keysByTableName as $tableName => $tableKeys ) {
-                       $res = $db->select( $tableName,
-                               array( 'keyname', 'value', 'exptime' ),
-                               array( 'keyname' => $tableKeys ),
-                               __METHOD__ );
-                       foreach ( $res as $row ) {
-                               $dataRows[$row->keyname] = $row;
+                       $dataRows = array();
+                       foreach ( $keysByTableName as $tableName => $tableKeys ) {
+                               $res = $db->select( $tableName,
+                                       array( 'keyname', 'value', 'exptime' ),
+                                       array( 'keyname' => $tableKeys ),
+                                       __METHOD__ );
+                               foreach ( $res as $row ) {
+                                       $dataRows[$row->keyname] = $row;
+                               }
                        }
-               }
 
-               foreach ( $keys as $key ) {
-                       if ( isset( $dataRows[$key] ) ) { // HIT?
-                               $row = $dataRows[$key];
-                               $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
-                               if ( $this->isExpired( $row->exptime ) ) { // MISS
-                                       $this->debug( "get: key has expired, deleting" );
-                                       try {
-                                               $db->begin( __METHOD__ );
-                                               # Put the expiry time in the WHERE condition to avoid deleting a
-                                               # newly-inserted value
-                                               $db->delete( $this->getTableByKey( $key ),
-                                                       array( 'keyname' => $key, 'exptime' => $row->exptime ),
-                                                       __METHOD__ );
-                                               $db->commit( __METHOD__ );
-                                       } catch ( DBQueryError $e ) {
-                                               $this->handleWriteError( $e );
+                       foreach ( $keys as $key ) {
+                               if ( isset( $dataRows[$key] ) ) { // HIT?
+                                       $row = $dataRows[$key];
+                                       $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+                                       if ( $this->isExpired( $row->exptime ) ) { // MISS
+                                               $this->debug( "get: key has expired, deleting" );
+                                               try {
+                                                       $db->begin( __METHOD__ );
+                                                       # Put the expiry time in the WHERE condition to avoid deleting a
+                                                       # newly-inserted value
+                                                       $db->delete( $this->getTableByKey( $key ),
+                                                               array( 'keyname' => $key, 'exptime' => $row->exptime ),
+                                                               __METHOD__ );
+                                                       $db->commit( __METHOD__ );
+                                               } catch ( DBQueryError $e ) {
+                                                       $this->handleWriteError( $e );
+                                               }
+                                               $values[$key] = false;
+                                       } else { // HIT
+                                               $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
                                        }
+                               } else { // MISS
                                        $values[$key] = false;
-                               } else { // HIT
-                                       $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
+                                       $this->debug( 'get: no matching rows' );
                                }
-                       } else { // MISS
-                               $values[$key] = false;
-                               $this->debug( 'get: no matching rows' );
                        }
-               }
+               } catch ( DBError $e ) {
+                       $this->handleReadError( $e );
+               };
 
                return $values;
        }
 
+       /**
+        * @param $key string
+        * @param $value mixed
+        * @param $exptime int
+        * @return bool
+        */
        public function set( $key, $value, $exptime = 0 ) {
-               $db = $this->getDB();
-               $exptime = intval( $exptime );
-
-               if ( $exptime < 0 ) {
-                       $exptime = 0;
-               }
+               try {
+                       $db = $this->getDB();
+                       $exptime = intval( $exptime );
 
-               if ( $exptime == 0 ) {
-                       $encExpiry = $this->getMaxDateTime();
-               } else {
-                       if ( $exptime < 3.16e8 ) { # ~10 years
-                               $exptime += time();
+                       if ( $exptime < 0 ) {
+                               $exptime = 0;
                        }
 
-                       $encExpiry = $db->timestamp( $exptime );
-               }
-               try {
+                       if ( $exptime == 0 ) {
+                               $encExpiry = $this->getMaxDateTime();
+                       } else {
+                               if ( $exptime < 3.16e8 ) { # ~10 years
+                                       $exptime += time();
+                               }
+
+                               $encExpiry = $db->timestamp( $exptime );
+                       }
                        $db->begin( __METHOD__ );
                        // (bug 24425) use a replace if the db supports it instead of
                        // delete/insert to avoid clashes with conflicting keynames
@@ -238,40 +267,46 @@ class SqlBagOStuff extends BagOStuff {
                                        'exptime' => $encExpiry
                                ), __METHOD__ );
                        $db->commit( __METHOD__ );
-               } catch ( DBQueryError $e ) {
+               } catch ( DBError $e ) {
                        $this->handleWriteError( $e );
-
                        return false;
                }
 
                return true;
        }
 
+       /**
+        * @param $key string
+        * @param $time int
+        * @return bool
+        */
        public function delete( $key, $time = 0 ) {
-               $db = $this->getDB();
-
                try {
+                       $db = $this->getDB();
                        $db->begin( __METHOD__ );
                        $db->delete(
                                $this->getTableByKey( $key ),
                                array( 'keyname' => $key ),
                                __METHOD__ );
                        $db->commit( __METHOD__ );
-               } catch ( DBQueryError $e ) {
+               } catch ( DBError $e ) {
                        $this->handleWriteError( $e );
-
                        return false;
                }
 
                return true;
        }
 
+       /**
+        * @param $key string
+        * @param $step int
+        * @return int|null
+        */
        public function incr( $key, $step = 1 ) {
-               $db = $this->getDB();
-               $tableName = $this->getTableByKey( $key );
-               $step = intval( $step );
-
                try {
+                       $db = $this->getDB();
+                       $tableName = $this->getTableByKey( $key );
+                       $step = intval( $step );
                        $db->begin( __METHOD__ );
                        $row = $db->selectRow(
                                $tableName,
@@ -307,34 +342,47 @@ class SqlBagOStuff extends BagOStuff {
                                $newValue = null;
                        }
                        $db->commit( __METHOD__ );
-               } catch ( DBQueryError $e ) {
+               } catch ( DBError $e ) {
                        $this->handleWriteError( $e );
-
                        return null;
                }
 
                return $newValue;
        }
 
+       /**
+        * @return Array
+        */
        public function keys() {
-               $db = $this->getDB();
                $result = array();
 
-               for ( $i = 0; $i < $this->shards; $i++ ) {
-                       $res = $db->select( $this->getTableByShard( $i ),
-                               array( 'keyname' ), false, __METHOD__ );
-                       foreach ( $res as $row ) {
-                               $result[] = $row->keyname;
+               try {
+                       $db = $this->getDB();
+                       for ( $i = 0; $i < $this->shards; $i++ ) {
+                               $res = $db->select( $this->getTableByShard( $i ),
+                                       array( 'keyname' ), false, __METHOD__ );
+                               foreach ( $res as $row ) {
+                                       $result[] = $row->keyname;
+                               }
                        }
+               } catch ( DBError $e ) {
+                       $this->handleReadError( $e );
                }
 
                return $result;
        }
 
+       /**
+        * @param $exptime string
+        * @return bool
+        */
        protected function isExpired( $exptime ) {
                return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time();
        }
 
+       /**
+        * @return string
+        */
        protected function getMaxDateTime() {
                if ( time() > 0x7fffffff ) {
                        return $this->getDB()->timestamp( 1 << 62 );
@@ -366,15 +414,16 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Delete objects from the database which expire before a certain date.
+        * @param $timestamp string
+        * @param $progressCallback bool|callback
         * @return bool
         */
        public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
-               $db = $this->getDB();
-               $dbTimestamp = $db->timestamp( $timestamp );
-               $totalSeconds = false;
-               $baseConds = array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) );
-
                try {
+                       $db = $this->getDB();
+                       $dbTimestamp = $db->timestamp( $timestamp );
+                       $totalSeconds = false;
+                       $baseConds = array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) );
                        for ( $i = 0; $i < $this->shards; $i++ ) {
                                $maxExpTime = false;
                                while ( true ) {
@@ -430,24 +479,28 @@ class SqlBagOStuff extends BagOStuff {
                                        }
                                }
                        }
-               } catch ( DBQueryError $e ) {
+               } catch ( DBError $e ) {
                        $this->handleWriteError( $e );
+                       return false;
                }
+
                return true;
        }
 
        public function deleteAll() {
-               $db = $this->getDB();
-
                try {
+                       $db = $this->getDB();
                        for ( $i = 0; $i < $this->shards; $i++ ) {
                                $db->begin( __METHOD__ );
                                $db->delete( $this->getTableByShard( $i ), '*', __METHOD__ );
                                $db->commit( __METHOD__ );
                        }
-               } catch ( DBQueryError $e ) {
+               } catch ( DBError $e ) {
                        $this->handleWriteError( $e );
+                       return false;
                }
+
+               return true;
        }
 
        /**
@@ -490,23 +543,40 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * Handle a DBQueryError which occurred during a write operation.
-        * Ignore errors which are due to a read-only database, rethrow others.
+        * Handle a DBError which occurred during a read operation.
         */
-       protected function handleWriteError( $exception ) {
-               $db = $this->getDB();
-
-               if ( !$db->wasReadOnlyError() ) {
-                       throw $exception;
+       protected function handleReadError( DBError $exception ) {
+               if ( $exception instanceof DBConnectionError ) {
+                       $this->connFailureTime  = time();
+                       $this->connFailureError = $exception;
                }
-
-               try {
-                       $db->rollback( __METHOD__ );
-               } catch ( DBQueryError $e ) {
+               wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
+               if ( $this->db ) {
+                       wfDebug( __METHOD__ . ": ignoring query error\n" );
+               } else {
+                       wfDebug( __METHOD__ . ": ignoring connection error\n" );
                }
+       }
 
-               wfDebug( __METHOD__ . ": ignoring query error\n" );
-               $db->ignoreErrors( false );
+       /**
+        * Handle a DBQueryError which occurred during a write operation.
+        */
+       protected function handleWriteError( DBError $exception ) {
+               if ( $exception instanceof DBConnectionError ) {
+                       $this->connFailureTime  = time();
+                       $this->connFailureError = $exception;
+               }
+               if ( $this->db && $this->db->wasReadOnlyError() ) {
+                       try {
+                               $this->db->rollback( __METHOD__ );
+                       } catch ( DBError $e ) {}
+               }
+               wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
+               if ( $this->db ) {
+                       wfDebug( __METHOD__ . ": ignoring query error\n" );
+               } else {
+                       wfDebug( __METHOD__ . ": ignoring connection error\n" );
+               }
        }
 
        /**