* Converted BagOStuff.php from the style of memcached-client.php to the standard...
authorTim Starling <tstarling@users.mediawiki.org>
Sat, 15 Aug 2009 03:45:19 +0000 (03:45 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Sat, 15 Aug 2009 03:45:19 +0000 (03:45 +0000)
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.

includes/Article.php
includes/BagOStuff.php
includes/LocalisationCache.php
includes/MessageCache.php
includes/ObjectCache.php
includes/db/Database.php
includes/db/DatabaseMysql.php
includes/db/DatabaseSqlite.php

index ee738d5..edd015b 100644 (file)
@@ -3080,6 +3080,9 @@ class Article {
         */
        public function viewUpdates() {
                global $wgDeferredUpdateList, $wgDisableCounters, $wgUser;
+               if ( wfReadOnly() ) {
+                       return;
+               }
                # Don't update page view counters on views from bot users (bug 14044)
                if( !$wgDisableCounters && !$wgUser->isAllowed('bot') && $this->getID() ) {
                        Article::incViewCount( $this->getID() );
index 8f548c8..2ec6153 100644 (file)
  *
  * @ingroup Cache
  */
-class BagOStuff {
-       var $debugmode;
+abstract class BagOStuff {
+       var $debugMode = false;
 
-       function __construct() {
-               $this->set_debug( false );
-       }
-
-       function set_debug($bool) {
-               $this->debugmode = $bool;
+       public function set_debug( $bool ) {
+               $this->debugMode = $bool;
        }
 
        /* *** THE GUTS OF THE OPERATION *** */
        /* Override these with functional things in subclasses */
 
-       function get($key) {
-               /* stub */
-               return false;
-       }
+       /**
+        * Get an item with the given key. Returns false if it does not exist.
+        * @param $key string
+        */
+       abstract public function get( $key );
 
-       function set($key, $value, $exptime=0) {
-               /* stub */
-               return false;
-       }
+       /**
+        * Set an item. 
+        * @param $key string
+        * @param $value mixed
+        * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+        */
+       abstract public function set( $key, $value, $exptime = 0 );
 
-       function delete($key, $time=0) {
-               /* stub */
-               return false;
-       }
+       /*
+        * Delete an item.
+        * @param $key string
+        * @param $time int Amount of time to delay the operation (mostly memcached-specific)
+        */
+       abstract public function delete( $key, $time = 0 );
 
-       function lock($key, $timeout = 0) {
+       public function lock( $key, $timeout = 0 ) {
                /* stub */
                return true;
        }
 
-       function unlock($key) {
+       public function unlock( $key ) {
                /* stub */
                return true;
        }
 
-       function keys() {
+       public function keys() {
                /* stub */
                return array();
        }
 
        /* *** Emulated functions *** */
        /* Better performance can likely be got with custom written versions */
-       function get_multi($keys) {
+       public function get_multi( $keys ) {
                $out = array();
-               foreach($keys as $key)
-                       $out[$key] = $this->get($key);
+               foreach ( $keys as $key ) {
+                       $out[$key] = $this->get( $key );
+               }
                return $out;
        }
 
-       function set_multi($hash, $exptime=0) {
-               foreach($hash as $key => $value)
-                       $this->set($key, $value, $exptime);
+       public function set_multi( $hash, $exptime = 0 ) {
+               foreach ( $hash as $key => $value ) {
+                       $this->set( $key, $value, $exptime );
+               }
        }
 
-       function add($key, $value, $exptime=0) {
-               if( $this->get($key) == false ) {
-                       $this->set($key, $value, $exptime);
+       public function add( $key, $value, $exptime = 0 ) {
+               if ( $this->get( $key ) == false ) {
+                       $this->set( $key, $value, $exptime );
                        return true;
                }
        }
 
-       function add_multi($hash, $exptime=0) {
-               foreach($hash as $key => $value)
-                       $this->add($key, $value, $exptime);
+       public function add_multi( $hash, $exptime = 0 ) {
+               foreach ( $hash as $key => $value ) {
+                       $this->add( $key, $value, $exptime );
+               }
        }
 
-       function delete_multi($keys, $time=0) {
-               foreach($keys as $key)
-                       $this->delete($key, $time);
+       public function delete_multi( $keys, $time = 0 ) {
+               foreach ( $keys as $key ) {
+                       $this->delete( $key, $time );
+               }
        }
 
-       function replace($key, $value, $exptime=0) {
-               if( $this->get($key) !== false )
-                       $this->set($key, $value, $exptime);
+       public function replace( $key, $value, $exptime = 0 ) {
+               if ( $this->get( $key ) !== false ) {
+                       $this->set( $key, $value, $exptime );
+               }
        }
 
-       function incr($key, $value=1) {
-               if ( !$this->lock($key) ) {
+       public function incr( $key, $value = 1 ) {
+               if ( !$this->lock( $key ) ) {
                        return false;
                }
-               $value = intval($value);
-               if($value < 0) $value = 0;
+               $value = intval( $value );
 
                $n = false;
-               if( ($n = $this->get($key)) !== false ) {
+               if ( ( $n = $this->get( $key ) ) !== false ) {
                        $n += $value;
-                       $this->set($key, $n); // exptime?
+                       $this->set( $key, $n ); // exptime?
                }
-               $this->unlock($key);
+               $this->unlock( $key );
                return $n;
        }
 
-       function decr($key, $value=1) {
-               if ( !$this->lock($key) ) {
-                       return false;
-               }
-               $value = intval($value);
-               if($value < 0) $value = 0;
-
-               $m = false;
-               if( ($n = $this->get($key)) !== false ) {
-                       $m = $n - $value;
-                       if($m < 0) $m = 0;
-                       $this->set($key, $m); // exptime?
-               }
-               $this->unlock($key);
-               return $m;
+       public function decr( $key, $value = 1 ) {
+               return $this->incr( $key, -$value );
        }
 
-       function _debug($text) {
-               if($this->debugmode)
-                       wfDebug("BagOStuff debug: $text\n");
+       public function debug( $text ) {
+               if ( $this->debugMode )
+                       wfDebug( "BagOStuff debug: $text\n" );
        }
 
        /**
         * Convert an optionally relative time to an absolute time
         */
-       static function convertExpiry( $exptime ) {
-               if(($exptime != 0) && ($exptime < 3600*24*30)) {
+       protected function convertExpiry( $exptime ) {
+               if ( ( $exptime != 0 ) && ( $exptime < 3600 * 24 * 30 ) ) {
                        return time() + $exptime;
                } else {
                        return $exptime;
@@ -182,30 +175,34 @@ class HashBagOStuff extends BagOStuff {
                $this->bag = array();
        }
 
-       function _expire($key) {
+       protected function expire( $key ) {
                $et = $this->bag[$key][1];
-               if(($et == 0) || ($et > time()))
+               if ( ( $et == 0 ) || ( $et > time() ) ) {
                        return false;
-               $this->delete($key);
+               }
+               $this->delete( $key );
                return true;
        }
 
-       function get($key) {
-               if( !isset( $this->bag[$key] ) )
+       function get( $key ) {
+               if ( !isset( $this->bag[$key] ) ) {
                        return false;
-               if($this->_expire($key))
+               }
+               if ( $this->expire( $key ) ) {
                        return false;
+               }
                return $this->bag[$key][0];
        }
 
-       function set($key,$value,$exptime=0) {
-               $this->bag[$key] = array( $value, BagOStuff::convertExpiry( $exptime ) );
+       function set( $key, $value, $exptime = 0 ) {
+               $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) );
        }
 
-       function delete($key,$time=0) {
-               if( !isset( $this->bag[$key] ) )
+       function delete( $key, $time = 0 ) {
+               if ( !isset( $this->bag[$key] ) ) {
                        return false;
-               unset($this->bag[$key]);
+               }
+               unset( $this->bag[$key] );
                return true;
        }
 
@@ -215,182 +212,187 @@ class HashBagOStuff extends BagOStuff {
 }
 
 /**
- * Generic class to store objects in a database
+ * Class to store objects in the database
  *
  * @ingroup Cache
  */
-abstract class SqlBagOStuff extends BagOStuff {
-       var $table;
-       var $lastexpireall = 0;
+class SqlBagOStuff extends BagOStuff {
+       var $lb, $db;
+       var $lastExpireAll = 0;
 
-       /**
-        * Constructor
-        *
-        * @param $tablename String: name of the table to use
-        */
-       function __construct($tablename = 'objectcache') {
-               $this->table = $tablename;
+       protected function getDB() {
+               if ( !isset( $this->lb ) ) {
+                       $this->lb = wfGetLBFactory()->newMainLB();
+                       $this->db = $this->lb->getConnection( DB_MASTER );
+                       $this->db->clearFlag( DBO_TRX );
+               }
+               return $this->db;
        }
 
-       function get($key) {
-               /* expire old entries if any */
+       public function get( $key ) {
+               # expire old entries if any
                $this->garbageCollect();
-
-               $res = $this->_query(
-                       "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key);
-               if(!$res) {
-                       $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
+               $db = $this->getDB();
+               $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
+                       array( 'keyname' => $key ), __METHOD__ );
+               if ( !$row ) {
+                       $this->debug( 'get: no matching rows' );
                        return false;
                }
-               if($row=$this->_fetchobject($res)) {
-                       $this->_debug("get: retrieved data; exp time is " . $row->exptime);
-                       if ( $row->exptime != $this->_maxdatetime() &&
-                         wfTimestamp( TS_UNIX, $row->exptime ) < time() )
-                       {
-                               $this->_debug("get: key has expired, deleting");
-                               $this->delete($key);
-                               return false;
+
+               $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+               if ( $this->isExpired( $row->exptime ) ) {
+                       $this->debug( "get: key has expired, deleting" );
+                       try {
+                               $db->begin();
+                               # Put the expiry time in the WHERE condition to avoid deleting a 
+                               # newly-inserted value
+                               $db->delete( 'objectcache', 
+                                       array( 
+                                               'keyname' => $key,
+                                               'exptime' => $row->exptime
+                                       ), __METHOD__ );
+                               $db->commit();
+                       } catch ( DBQueryError $e ) {
+                               $this->handleWriteError( $e );
                        }
-                       return $this->_unserialize($this->_blobdecode($row->value));
-               } else {
-                       $this->_debug('get: no matching rows');
+                       return false;
                }
-               return false;
+               return $this->unserialize( $db->decodeBlob( $row->value ) );
        }
 
-       function set($key,$value,$exptime=0) {
-               if ( $this->_readonly() ) {
-                       return false;
-               }
-               $exptime = intval($exptime);
-               if($exptime < 0) $exptime = 0;
-               if($exptime == 0) {
-                       $exp = $this->_maxdatetime();
+       public function set( $key, $value, $exptime = 0 ) {
+               $db = $this->getDB();
+               $exptime = intval( $exptime );
+               if ( $exptime < 0 ) $exptime = 0;
+               if ( $exptime == 0 ) {
+                       $encExpiry = $this->getMaxDateTime();
                } else {
-                       if($exptime < 3.16e8) # ~10 years
+                       if ( $exptime < 3.16e8 ) # ~10 years
                                $exptime += time();
-                       $exp = $this->_fromunixtime($exptime);
+                       $encExpiry = $db->timestamp( $exptime );
                }
-               $this->_begin();
-               $this->_query(
-                       "DELETE FROM $0 WHERE keyname='$1'", $key );
-               $this->_doinsert($this->getTableName(), array(
+               try {
+                       $db->begin();
+                       $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
+                       $db->insert( 'objectcache', 
+                               array( 
                                        'keyname' => $key,
-                                       'value' => $this->_blobencode($this->_serialize($value)),
-                                       'exptime' => $exp
-                               ));
-               $this->_commit();
-               return true; /* ? */
+                                       'value' => $db->encodeBlob( $this->serialize( $value ) ),
+                                       'exptime' => $encExpiry
+                               ), __METHOD__ );
+                       $db->commit();
+               } catch ( DBQueryError $e ) {
+                       $this->handleWriteError( $e );
+                       return false;
+               }
+               return true;
        }
 
-       function delete($key,$time=0) {
-               if ( $this->_readonly() ) {
+       public function delete( $key, $time = 0 ) {
+               $db = $this->getDB();
+               try {
+                       $db->begin();
+                       $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
+                       $db->commit();
+               } catch ( DBQueryError $e ) {
+                       $this->handleWriteError( $e );
                        return false;
                }
-               $this->_begin();
-               $this->_query(
-                       "DELETE FROM $0 WHERE keyname='$1'", $key );
-               $this->_commit();
-               return true; /* ? */
+               return true;
        }
 
-       function keys() {
-               $res = $this->_query( "SELECT keyname FROM $0" );
-               if(!$res) {
-                       $this->_debug("keys: ** error: " . $this->_dberror($res) . " **");
-                       return array();
+       public function incr( $key, $step = 1 ) {
+               $db = $this->getDB();
+               $step = intval( $step );
+
+               try {
+                       $db->begin();
+                       $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
+                               array( 'keyname' => $key ), __METHOD__, array( 'FOR UPDATE' ) );
+                       if ( $row === false ) {
+                               // Missing
+                               $db->commit();
+                               return false;
+                       }
+                       $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
+                       if ( $this->isExpired( $row->exptime ) ) {
+                               // Expired, do not reinsert
+                               $db->commit();
+                               return false;
+                       }
+
+                       $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) );
+                       $newValue = $oldValue + $step;
+                       $db->insert( 'objectcache',
+                               array(
+                                       'keyname' => $key,
+                                       'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
+                                       'exptime' => $row->exptime
+                               ), __METHOD__ );
+                       $db->commit();
+               } catch ( DBQueryError $e ) {
+                       $this->handleWriteError( $e );
+                       return false;
                }
+               return $newValue;
+       }
+
+       public function keys() {
+               $db = $this->getDB();
+               $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ );     
                $result = array();
-               while( $row = $this->_fetchobject($res) ) {
+               foreach ( $res as $row ) {
                        $result[] = $row->keyname;
                }
                return $result;
        }
 
-       function getTableName() {
-               return $this->table;
+       protected function isExpired( $exptime ) {
+               return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time();
        }
 
-       function _query($sql) {
-               $reps = func_get_args();
-               $reps[0] = $this->getTableName();
-               // ewwww
-               for($i=0;$i<count($reps);$i++) {
-                       $sql = str_replace(
-                               '$' . $i,
-                               $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i],
-                               $sql);
-               }
-               $res = $this->_doquery($sql);
-               if($res == false) {
-                       $this->_debug('query failed: ' . $this->_dberror($res));
+       protected function getMaxDateTime() {
+               if ( time() > 0x7fffffff ) {
+                       return $this->getDB()->timestamp( 1 << 62 );
+               } else {
+                       return $this->getDB()->timestamp( 0x7fffffff );
                }
-               return $res;
-       }
-
-       function _strencode($str) {
-               /* Protect strings in SQL */
-               return str_replace( "'", "''", $str );
-       }
-       function _blobencode($str) {
-               return $str;
-       }
-       function _blobdecode($str) {
-               return $str;
-       }
-
-       abstract function _doinsert($table, $vals);
-       abstract function _doquery($sql);
-
-       abstract function _readonly();
-
-       function _begin() {}
-       function _commit() {}
-
-       function _freeresult($result) {
-               /* stub */
-               return false;
-       }
-
-       function _dberror($result) {
-               /* stub */
-               return 'unknown error';
        }
 
-       abstract function _maxdatetime();
-       abstract function _fromunixtime($ts);
-
-       function garbageCollect() {
+       protected function garbageCollect() {
                /* Ignore 99% of requests */
                if ( !mt_rand( 0, 100 ) ) {
-                       $nowtime = time();
+                       $now = time();
                        /* Avoid repeating the delete within a few seconds */
-                       if ( $nowtime > ($this->lastexpireall + 1) ) {
-                               $this->lastexpireall = $nowtime;
-                               $this->expireall();
+                       if ( $now > ( $this->lastExpireAll + 1 ) ) {
+                               $this->lastExpireAll = $now;
+                               $this->expireAll();
                        }
                }
        }
 
-       function expireall() {
-               /* Remove any items that have expired */
-               if ( $this->_readonly() ) {
-                       return false;
+       public function expireAll() {
+               $db = $this->getDB();
+               $now = $db->timestamp();
+               try {
+                       $db->begin();
+                       $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ );
+                       $db->commit();
+               } catch ( DBQueryError $e ) {
+                       $this->handleWriteError( $e );
                }
-               $now = $this->_fromunixtime( time() );
-               $this->_begin();
-               $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" );
-               $this->_commit();
        }
 
-       function deleteall() {
-               /* Clear *all* items from cache table */
-               if ( $this->_readonly() ) {
-                       return false;
+       public function deleteAll() {
+               $db = $this->getDB();
+               try {
+                       $db->begin();
+                       $db->delete( 'objectcache', '*', __METHOD__ );
+                       $db->commit();
+               } catch ( DBQueryError $e ) {
+                       $this->handleWriteError( $e );
                }
-               $this->_begin();
-               $this->_query( "DELETE FROM $0" );
-               $this->_commit();
        }
 
        /**
@@ -401,9 +403,9 @@ abstract class SqlBagOStuff extends BagOStuff {
         * @param $data mixed
         * @return string
         */
-       function _serialize( &$data ) {
+       protected function serialize( &$data ) {
                $serial = serialize( $data );
-               if( function_exists( 'gzdeflate' ) ) {
+               if ( function_exists( 'gzdeflate' ) ) {
                        return gzdeflate( $serial );
                } else {
                        return $serial;
@@ -415,103 +417,40 @@ abstract class SqlBagOStuff extends BagOStuff {
         * @param $serial string
         * @return mixed
         */
-       function _unserialize( $serial ) {
-               if( function_exists( 'gzinflate' ) ) {
+       protected function unserialize( $serial ) {
+               if ( function_exists( 'gzinflate' ) ) {
                        $decomp = @gzinflate( $serial );
-                       if( false !== $decomp ) {
+                       if ( false !== $decomp ) {
                                $serial = $decomp;
                        }
                }
                $ret = unserialize( $serial );
                return $ret;
        }
-}
-
-/**
- * Stores objects in the main database of the wiki
- *
- * @ingroup Cache
- */
-class MediaWikiBagOStuff extends SqlBagOStuff {
-       var $tableInitialised = false;
-       var $lb, $db;
 
-       function _getDB(){
-               if ( !isset( $this->lb ) ) {
-                       $this->lb = wfGetLBFactory()->newMainLB();
-                       $this->db = $this->lb->getConnection( DB_MASTER );
-                       $this->db->clearFlag( DBO_TRX );
-               }
-               return $this->db;
-       }
-       function _begin() {
-               $this->_getDB()->begin();
-       }
-       function _commit() {
-               $this->_getDB()->commit();
-       }
-       function _doquery($sql) {
-               return $this->_getDB()->query( $sql, __METHOD__ );
-       }
-       function _doinsert($t, $v) {
-               return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) );
-       }
-       function _fetchobject($result) {
-               return $this->_getDB()->fetchObject($result);
-       }
-       function _freeresult($result) {
-               return $this->_getDB()->freeResult($result);
-       }
-       function _dberror($result) {
-               return $this->_getDB()->lastError();
-       }
-       function _maxdatetime() {
-               if ( time() > 0x7fffffff ) {
-                       return $this->_fromunixtime( 1<<62 );
-               } else {
-                       return $this->_fromunixtime( 0x7fffffff );
-               }
-       }
-       function _fromunixtime($ts) {
-               return $this->_getDB()->timestamp($ts);
-       }
-       /***
-        * Note -- this should *not* check wfReadOnly().
-        * Read-only mode has been repurposed from the original
-        * "nothing must write to the database" to "users should not
-        * be able to edit or alter anything user-visible".
-        *
-        * Backend bits like the object cache should continue
-        * to work in this mode, otherwise things will blow up
-        * like the message cache failing to save its state,
-        * causing long delays (bug 11533).
+       /**
+        * Handle a DBQueryError which occurred during a write operation.
+        * Ignore errors which are due to a read-only database, rethrow others.
         */
-       function _readonly(){
-               return false;
-       }
-       function _strencode($s) {
-               return $this->_getDB()->strencode($s);
-       }
-       function _blobencode($s) {
-               return $this->_getDB()->encodeBlob($s);
-       }
-       function _blobdecode($s) {
-               return $this->_getDB()->decodeBlob($s);
-       }
-       function getTableName() {
-               if ( !$this->tableInitialised ) {
-                       $dbw = $this->_getDB();
-                       /* This is actually a hack, we should be able
-                          to use Language classes here... or not */
-                       if (!$dbw)
-                               throw new MWException("Could not connect to database");
-                       $this->table = $dbw->tableName( $this->table );
-                       $this->tableInitialised = true;
+       protected function handleWriteError( $exception ) {
+               $db = $this->getDB();
+               if ( !$db->wasReadOnlyError() ) {
+                       throw $exception;
                }
-               return $this->table;
+               try {
+                       $db->rollback();
+               } catch ( DBQueryError $e ) {
+               }
+               wfDebug( __METHOD__ . ": ignoring query error\n" );
+               $db->ignoreErrors( false );
        }
 }
 
+/**
+ * Backwards compatibility alias
+ */
+class MediaWikiBagOStuff extends SqlBagOStuff {}
+
 /**
  * This is a wrapper for Turck MMCache's shared memory functions.
  *
@@ -528,7 +467,7 @@ class MediaWikiBagOStuff extends SqlBagOStuff {
  * @ingroup Cache
  */
 class TurckBagOStuff extends BagOStuff {
-       function get($key) {
+       public function get( $key ) {
                $val = mmcache_get( $key );
                if ( is_string( $val ) ) {
                        $val = unserialize( $val );
@@ -536,22 +475,22 @@ class TurckBagOStuff extends BagOStuff {
                return $val;
        }
 
-       function set($key, $value, $exptime=0) {
+       public function set( $key, $value, $exptime = 0 ) {
                mmcache_put( $key, serialize( $value ), $exptime );
                return true;
        }
 
-       function delete($key, $time=0) {
+       public function delete( $key, $time = 0 ) {
                mmcache_rm( $key );
                return true;
        }
 
-       function lock($key, $waitTimeout = 0 ) {
+       public function lock( $key, $waitTimeout = 0 ) {
                mmcache_lock( $key );
                return true;
        }
 
-       function unlock($key) {
+       public function unlock( $key ) {
                mmcache_unlock( $key );
                return true;
        }
@@ -563,21 +502,21 @@ class TurckBagOStuff extends BagOStuff {
  * @ingroup Cache
  */
 class APCBagOStuff extends BagOStuff {
-       function get($key) {
-               $val = apc_fetch($key);
+       public function get( $key ) {
+               $val = apc_fetch( $key );
                if ( is_string( $val ) ) {
                        $val = unserialize( $val );
                }
                return $val;
        }
 
-       function set($key, $value, $exptime=0) {
-               apc_store($key, serialize($value), $exptime);
+       public function set( $key, $value, $exptime = 0 ) {
+               apc_store( $key, serialize( $value ), $exptime );
                return true;
        }
 
-       function delete($key, $time=0) {
-               apc_delete($key);
+       public function delete( $key, $time = 0 ) {
+               apc_delete( $key );
                return true;
        }
 }
@@ -592,7 +531,7 @@ class APCBagOStuff extends BagOStuff {
  * @ingroup Cache
  */
 class eAccelBagOStuff extends BagOStuff {
-       function get($key) {
+       public function get( $key ) {
                $val = eaccelerator_get( $key );
                if ( is_string( $val ) ) {
                        $val = unserialize( $val );
@@ -600,22 +539,22 @@ class eAccelBagOStuff extends BagOStuff {
                return $val;
        }
 
-       function set($key, $value, $exptime=0) {
+       public function set( $key, $value, $exptime = 0 ) {
                eaccelerator_put( $key, serialize( $value ), $exptime );
                return true;
        }
 
-       function delete($key, $time=0) {
+       public function delete( $key, $time = 0 ) {
                eaccelerator_rm( $key );
                return true;
        }
 
-       function lock($key, $waitTimeout = 0 ) {
+       public function lock( $key, $waitTimeout = 0 ) {
                eaccelerator_lock( $key );
                return true;
        }
 
-       function unlock($key) {
+       public function unlock( $key ) {
                eaccelerator_unlock( $key );
                return true;
        }
@@ -637,7 +576,7 @@ class XCacheBagOStuff extends BagOStuff {
         */
        public function get( $key ) {
                $val = xcache_get( $key );
-               if( is_string( $val ) )
+               if ( is_string( $val ) )
                        $val = unserialize( $val );
                return $val;
        }
@@ -670,20 +609,24 @@ class XCacheBagOStuff extends BagOStuff {
 }
 
 /**
- * @todo document
+ * Cache that uses DBA as a backend. 
+ * Slow due to the need to constantly open and close the file to avoid holding 
+ * writer locks. Intended for development use only,  as a memcached workalike 
+ * for systems that don't have it.
+ *
  * @ingroup Cache
  */
 class DBABagOStuff extends BagOStuff {
        var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
 
-       function __construct( $handler = 'db3', $dir = false ) {
+       public function __construct( $handler = 'db3', $dir = false ) {
                if ( $dir === false ) {
                        global $wgTmpDirectory;
                        $dir = $wgTmpDirectory;
                }
                $this->mFile = "$dir/mw-cache-" . wfWikiID();
                $this->mFile .= '.db';
-               wfDebug( __CLASS__.": using cache file {$this->mFile}\n" );
+               wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" );
                $this->mHandler = $handler;
        }
 
@@ -692,7 +635,7 @@ class DBABagOStuff extends BagOStuff {
         */
        function encode( $value, $expiry ) {
                # Convert to absolute time
-               $expiry = BagOStuff::convertExpiry( $expiry );
+               $expiry = $this->convertExpiry( $expiry );
                return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
        }
 
@@ -732,7 +675,7 @@ class DBABagOStuff extends BagOStuff {
 
        function get( $key ) {
                wfProfileIn( __METHOD__ );
-               wfDebug( __METHOD__."($key)\n" );
+               wfDebug( __METHOD__ . "($key)\n" );
                $handle = $this->getReader();
                if ( !$handle ) {
                        return null;
@@ -747,16 +690,16 @@ class DBABagOStuff extends BagOStuff {
                        $handle = $this->getWriter();
                        dba_delete( $key, $handle );
                        dba_close( $handle );
-                       wfDebug( __METHOD__.": $key expired\n" );
+                       wfDebug( __METHOD__ . ": $key expired\n" );
                        $val = null;
                }
                wfProfileOut( __METHOD__ );
                return $val;
        }
 
-       function set( $key, $value, $exptime=0 ) {
+       function set( $key, $value, $exptime = 0 ) {
                wfProfileIn( __METHOD__ );
-               wfDebug( __METHOD__."($key)\n" );
+               wfDebug( __METHOD__ . "($key)\n" );
                $blob = $this->encode( $value, $exptime );
                $handle = $this->getWriter();
                if ( !$handle ) {
@@ -770,7 +713,7 @@ class DBABagOStuff extends BagOStuff {
 
        function delete( $key, $time = 0 ) {
                wfProfileIn( __METHOD__ );
-               wfDebug( __METHOD__."($key)\n" );
+               wfDebug( __METHOD__ . "($key)\n" );
                $handle = $this->getWriter();
                if ( !$handle ) {
                        return false;
@@ -808,11 +751,11 @@ class DBABagOStuff extends BagOStuff {
        function keys() {
                $reader = $this->getReader();
                $k1 = dba_firstkey( $reader );
-               if( !$k1 ) {
+               if ( !$k1 ) {
                        return array();
                }
                $result[] = $k1;
-               while( $key = dba_nextkey( $reader ) ) {
+               while ( $key = dba_nextkey( $reader ) ) {
                        $result[] = $key;
                }
                return $result;
index d4dfbc5..561fc39 100644 (file)
@@ -723,6 +723,7 @@ class LCStore_DB implements LCStore {
        var $currentLang;
        var $writesDone = false;
        var $dbw, $batch;
+       var $readOnly = false;
 
        public function get( $code, $key ) {
                if ( $this->writesDone ) {
@@ -740,17 +741,34 @@ class LCStore_DB implements LCStore {
        }
 
        public function startWrite( $code ) {
+               if ( $this->readOnly ) {
+                       return;
+               }
                if ( !$code ) {
                        throw new MWException( __METHOD__.": Invalid language \"$code\"" );
                }
                $this->dbw = wfGetDB( DB_MASTER );
-               $this->dbw->begin();
-               $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
+               try {
+                       $this->dbw->begin();
+                       $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
+               } catch ( DBQueryError $e ) {
+                       if ( $this->dbw->wasReadOnlyError() ) {
+                               $this->readOnly = true;
+                               $this->dbw->rollback();
+                               $this->dbw->ignoreErrors( false );
+                               return;
+                       } else {
+                               throw $e;
+                       }
+               }
                $this->currentLang = $code;
                $this->batch = array();
        }
 
        public function finishWrite() {
+               if ( $this->readOnly ) {
+                       return;
+               }
                if ( $this->batch ) {
                        $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
                }
@@ -762,6 +780,9 @@ class LCStore_DB implements LCStore {
        }
 
        public function set( $key, $value ) {
+               if ( $this->readOnly ) {
+                       return;
+               }
                if ( is_null( $this->currentLang ) ) {
                        throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
                }
index e7679b1..96c00ac 100644 (file)
@@ -432,16 +432,10 @@ class MessageCache {
 
                $cacheKey = wfMemcKey( 'messages', $code );
 
-               $i = 0;
                if ( $memc ) {
-                       # Save in memcached
-                       # Keep trying if it fails, this is kind of important
-
-                       for ($i=0; $i<20 &&
-                               !$this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
-                               $i++ ) {
-                               usleep(mt_rand(500000,1500000));
-                       }
+                       $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
+               } else {
+                       $success = true;
                }
 
                # Save to local cache
@@ -456,11 +450,6 @@ class MessageCache {
                        }
                }
 
-               if ( $i == 20 ) {
-                       $success = false;
-               } else {
-                       $success = true;
-               }
                wfProfileOut( __METHOD__ );
                return $success;
        }
index 0ee6a92..d4f5084 100644 (file)
@@ -91,7 +91,7 @@ function &wfGetCache( $inputType ) {
 
        if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) {
                if ( !array_key_exists( CACHE_DB, $wgCaches ) ) {
-                       $wgCaches[CACHE_DB] = new MediaWikiBagOStuff('objectcache');
+                       $wgCaches[CACHE_DB] = new SqlBagOStuff('objectcache');
                }
                $cache =& $wgCaches[CACHE_DB];
        }
index c52c6ab..9ccb00a 100644 (file)
@@ -1715,17 +1715,27 @@ abstract class DatabaseBase {
 
        /**
         * Determines if the last failure was due to a deadlock
+        * STUB
         */
        function wasDeadlock() {
-               return $this->lastErrno() == 1213;
+               return false;
        }
 
        /**
         * Determines if the last query error was something that should be dealt 
-        * with by pinging the connection and reissuing the query
+        * with by pinging the connection and reissuing the query.
+        * STUB
         */
        function wasErrorReissuable() {
-               return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+               return false;
+       }
+
+       /**
+        * Determines if the last failure was due to the database being read-only.
+        * STUB
+        */
+       function wasReadOnlyError() {
+               return false;
        }
 
        /**
index 104d140..c2a5093 100644 (file)
@@ -360,6 +360,31 @@ class DatabaseMysql extends DatabaseBase {
                $encValue = $value ? '1' : '0';
                $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
        }
+
+       
+       /**
+        * Determines if the last failure was due to a deadlock
+        */
+       function wasDeadlock() {
+               return $this->lastErrno() == 1213;
+       }
+
+       /**
+        * Determines if the last query error was something that should be dealt 
+        * with by pinging the connection and reissuing the query
+        */
+       function wasErrorReissuable() {
+               return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+       }
+
+       /**
+        * Determines if the last failure was due to the database being read-only.
+        */
+       function wasReadOnlyError() {
+               return $this->lastErrno() == 1223 || 
+                       ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
+       }
+
 }
 
 /**
index 43ba8bc..01b6b2b 100644 (file)
@@ -280,6 +280,10 @@ class DatabaseSqlite extends DatabaseBase {
                return $this->lastErrno() ==  SQLITE_SCHEMA;
        }
 
+       function wasReadOnlyError() {
+               return $this->lastErrno() == SQLITE_READONLY;
+       }
+
        /**
         * @return string wikitext of a link to the server software's web site
         */