From: Tim Starling Date: Thu, 3 Mar 2011 04:38:17 +0000 (+0000) Subject: Start of ObjectCache reorganisation. Moved the object cache files to includes/objectc... X-Git-Tag: 1.31.0-rc.0~31680 X-Git-Url: http://git.cyclocoop.org/%40spipnet%40?a=commitdiff_plain;h=af04dde2901bf9303ebf6c9e8278b854736902c7;p=lhc%2Fweb%2Fwiklou.git Start of ObjectCache reorganisation. Moved the object cache files to includes/objectcache/. Split BagOStuff.php into single-class files. --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index c43240b125..8717648b56 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -17,14 +17,12 @@ $wgAutoloadLocalClasses = array( 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', 'AjaxResponse' => 'includes/AjaxResponse.php', 'AlphabeticPager' => 'includes/Pager.php', - 'APCBagOStuff' => 'includes/BagOStuff.php', 'Article' => 'includes/Article.php', 'AtomFeed' => 'includes/Feed.php', 'AuthPlugin' => 'includes/AuthPlugin.php', 'AuthPluginUser' => 'includes/AuthPlugin.php', 'Autopromote' => 'includes/Autopromote.php', 'BacklinkCache' => 'includes/BacklinkCache.php', - 'BagOStuff' => 'includes/BagOStuff.php', 'Block' => 'includes/Block.php', 'CacheDependency' => 'includes/CacheDependency.php', 'CacheTime' => 'includes/parser/ParserOutput.php', @@ -55,7 +53,6 @@ $wgAutoloadLocalClasses = array( 'Credits' => 'includes/Credits.php', 'CSSJanus' => 'includes/libs/CSSJanus.php', 'CSSMin' => 'includes/libs/CSSMin.php', - 'DBABagOStuff' => 'includes/BagOStuff.php', 'DependencyWrapper' => 'includes/CacheDependency.php', 'DiffHistoryBlob' => 'includes/HistoryBlob.php', 'DjVuImage' => 'includes/DjVuImage.php', @@ -72,7 +69,6 @@ $wgAutoloadLocalClasses = array( 'DumpNotalkFilter' => 'includes/Export.php', 'DumpOutput' => 'includes/Export.php', 'DumpPipeOutput' => 'includes/Export.php', - 'eAccelBagOStuff' => 'includes/BagOStuff.php', 'EditPage' => 'includes/EditPage.php', 'EmailNotification' => 'includes/UserMailer.php', 'EnhancedChangesList' => 'includes/ChangesList.php', @@ -86,7 +82,6 @@ $wgAutoloadLocalClasses = array( 'ExternalUser' => 'includes/ExternalUser.php', 'FatalError' => 'includes/Exception.php', 'FakeTitle' => 'includes/FakeTitle.php', - 'FakeMemCachedClient' => 'includes/ObjectCache.php', 'Fallback' => 'includes/Fallback.php', 'FauxRequest' => 'includes/WebRequest.php', 'FauxResponse' => 'includes/WebResponse.php', @@ -100,7 +95,6 @@ $wgAutoloadLocalClasses = array( 'FormOptions' => 'includes/FormOptions.php', 'GenderCache' => 'includes/GenderCache.php', 'GlobalDependency' => 'includes/CacheDependency.php', - 'HashBagOStuff' => 'includes/BagOStuff.php', 'HashtableReplacer' => 'includes/StringUtils.php', 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HistoryBlob' => 'includes/HistoryBlob.php', @@ -162,7 +156,6 @@ $wgAutoloadLocalClasses = array( 'MagicWord' => 'includes/MagicWord.php', 'MailAddress' => 'includes/UserMailer.php', 'MathRenderer' => 'includes/Math.php', - 'MediaWikiBagOStuff' => 'includes/BagOStuff.php', 'MediaWiki_I18N' => 'includes/SkinTemplate.php', 'MediaWiki' => 'includes/Wiki.php', 'MemCachedClientforWiki' => 'includes/memcached-client.php', @@ -232,7 +225,6 @@ $wgAutoloadLocalClasses = array( 'SpecialMytalk' => 'includes/SpecialPage.php', 'SpecialPage' => 'includes/SpecialPage.php', 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php', - 'SqlBagOStuff' => 'includes/BagOStuff.php', 'SquidUpdate' => 'includes/SquidUpdate.php', 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php', @@ -267,8 +259,6 @@ $wgAutoloadLocalClasses = array( 'WikiMap' => 'includes/WikiMap.php', 'WikiReference' => 'includes/WikiMap.php', 'WikiXmlError' => 'includes/WikiError.php', - 'WinCacheBagOStuff' => 'includes/BagOStuff.php', - 'XCacheBagOStuff' => 'includes/BagOStuff.php', 'XmlDumpWriter' => 'includes/Export.php', 'Xml' => 'includes/Xml.php', 'XmlJsCode' => 'includes/Xml.php', @@ -512,6 +502,18 @@ $wgAutoloadLocalClasses = array( # includes/normal 'UtfNormal' => 'includes/normal/UtfNormal.php', + # includes/objectcache + 'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php', + 'BagOStuff' => 'includes/objectcache/BagOStuff.php', + 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php', + 'FakeMemCachedClient' => 'includes/objectcache/ObjectCache.php', + 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php', + 'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php', + 'SqlBagOStuff' => 'includes/objectcache/SqlBagOStuff.php', + 'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php', + 'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php', + 'eAccelBagOStuff' => 'includes/objectcache/eAccelBagOStuff.php', + # includes/parser 'CoreLinkFunctions' => 'includes/parser/CoreLinkFunctions.php', 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php deleted file mode 100644 index 1caaa1c440..0000000000 --- a/includes/BagOStuff.php +++ /dev/null @@ -1,905 +0,0 @@ - - * http://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Cache - */ - -/** - * @defgroup Cache Cache - */ - -/** - * interface is intended to be more or less compatible with - * the PHP memcached client. - * - * backends for local hash array and SQL table included: - * - * $bag = new HashBagOStuff(); - * $bag = new SqlBagOStuff(); # connect to db first - * - * - * @ingroup Cache - */ -abstract class BagOStuff { - var $debugMode = false; - - public function set_debug( $bool ) { - $this->debugMode = $bool; - } - - /* *** THE GUTS OF THE OPERATION *** */ - /* Override these with functional things in subclasses */ - - /** - * Get an item with the given key. Returns false if it does not exist. - * @param $key string - */ - abstract public function get( $key ); - - /** - * 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 ); - - /* - * 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 ); - - public function lock( $key, $timeout = 0 ) { - /* stub */ - return true; - } - - public function unlock( $key ) { - /* stub */ - return true; - } - - public function keys() { - /* stub */ - return array(); - } - - /* *** Emulated functions *** */ - /* Better performance can likely be got with custom written versions */ - public function get_multi( $keys ) { - $out = array(); - - foreach ( $keys as $key ) { - $out[$key] = $this->get( $key ); - } - - return $out; - } - - public function set_multi( $hash, $exptime = 0 ) { - foreach ( $hash as $key => $value ) { - $this->set( $key, $value, $exptime ); - } - } - - public function add( $key, $value, $exptime = 0 ) { - if ( !$this->get( $key ) ) { - $this->set( $key, $value, $exptime ); - - return true; - } - } - - public function add_multi( $hash, $exptime = 0 ) { - foreach ( $hash as $key => $value ) { - $this->add( $key, $value, $exptime ); - } - } - - public function delete_multi( $keys, $time = 0 ) { - foreach ( $keys as $key ) { - $this->delete( $key, $time ); - } - } - - public function replace( $key, $value, $exptime = 0 ) { - if ( $this->get( $key ) !== false ) { - $this->set( $key, $value, $exptime ); - } - } - - /** - * @param $key String: Key to increase - * @param $value Integer: Value to add to $key (Default 1) - * @return null if lock is not possible else $key value increased by $value - */ - public function incr( $key, $value = 1 ) { - if ( !$this->lock( $key ) ) { - return null; - } - - $value = intval( $value ); - - if ( ( $n = $this->get( $key ) ) !== false ) { - $n += $value; - $this->set( $key, $n ); // exptime? - } - $this->unlock( $key ); - - return $n; - } - - public function decr( $key, $value = 1 ) { - return $this->incr( $key, - $value ); - } - - public function debug( $text ) { - if ( $this->debugMode ) { - wfDebug( "BagOStuff debug: $text\n" ); - } - } - - /** - * Convert an optionally relative time to an absolute time - */ - protected function convertExpiry( $exptime ) { - if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) { - return time() + $exptime; - } else { - return $exptime; - } - } -} - -/** - * Functional versions! - * This is a test of the interface, mainly. It stores things in an associative - * array, which is not going to persist between program runs. - * - * @ingroup Cache - */ -class HashBagOStuff extends BagOStuff { - var $bag; - - function __construct() { - $this->bag = array(); - } - - protected function expire( $key ) { - $et = $this->bag[$key][1]; - - if ( ( $et == 0 ) || ( $et > time() ) ) { - return false; - } - - $this->delete( $key ); - - return true; - } - - function get( $key ) { - if ( !isset( $this->bag[$key] ) ) { - return false; - } - - if ( $this->expire( $key ) ) { - return false; - } - - return $this->bag[$key][0]; - } - - function set( $key, $value, $exptime = 0 ) { - $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) ); - } - - function delete( $key, $time = 0 ) { - if ( !isset( $this->bag[$key] ) ) { - return false; - } - - unset( $this->bag[$key] ); - - return true; - } - - function keys() { - return array_keys( $this->bag ); - } -} - -/** - * Class to store objects in the database - * - * @ingroup Cache - */ -class SqlBagOStuff extends BagOStuff { - var $lb, $db; - var $lastExpireAll = 0; - - protected function getDB() { - if ( !isset( $this->db ) ) { - /* We must keep a separate connection to MySQL in order to avoid deadlocks - * However, SQLite has an opposite behaviour. - * @todo Investigate behaviour for other databases - */ - if ( wfGetDB( DB_MASTER )->getType() == 'sqlite' ) { - $this->db = wfGetDB( DB_MASTER ); - } else { - $this->lb = wfGetLBFactory()->newMainLB(); - $this->db = $this->lb->getConnection( DB_MASTER ); - $this->db->clearFlag( DBO_TRX ); - } - } - - return $this->db; - } - - public function get( $key ) { - # expire old entries if any - $this->garbageCollect(); - $db = $this->getDB(); - $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), - array( 'keyname' => $key ), __METHOD__ ); - - if ( !$row ) { - $this->debug( 'get: no matching rows' ); - 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 false; - } - - return $this->unserialize( $db->decodeBlob( $row->value ) ); - } - - 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 - $exptime += time(); - } - - $encExpiry = $db->timestamp( $exptime ); - } - try { - $db->begin(); - // (bug 24425) use a replace if the db supports it instead of - // delete/insert to avoid clashes with conflicting keynames - $db->replace( 'objectcache', array( 'keyname' ), - array( - 'keyname' => $key, - 'value' => $db->encodeBlob( $this->serialize( $value ) ), - 'exptime' => $encExpiry - ), __METHOD__ ); - $db->commit(); - } catch ( DBQueryError $e ) { - $this->handleWriteError( $e ); - - return false; - } - - return true; - } - - 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; - } - - return true; - } - - 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 null; - } - $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); - if ( $this->isExpired( $row->exptime ) ) { - // Expired, do not reinsert - $db->commit(); - - return null; - } - - $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 null; - } - - return $newValue; - } - - public function keys() { - $db = $this->getDB(); - $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ ); - $result = array(); - - foreach ( $res as $row ) { - $result[] = $row->keyname; - } - - return $result; - } - - protected function isExpired( $exptime ) { - return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time(); - } - - protected function getMaxDateTime() { - if ( time() > 0x7fffffff ) { - return $this->getDB()->timestamp( 1 << 62 ); - } else { - return $this->getDB()->timestamp( 0x7fffffff ); - } - } - - protected function garbageCollect() { - /* Ignore 99% of requests */ - if ( !mt_rand( 0, 100 ) ) { - $now = time(); - /* Avoid repeating the delete within a few seconds */ - if ( $now > ( $this->lastExpireAll + 1 ) ) { - $this->lastExpireAll = $now; - $this->expireAll(); - } - } - } - - 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 ); - } - } - - public function deleteAll() { - $db = $this->getDB(); - - try { - $db->begin(); - $db->delete( 'objectcache', '*', __METHOD__ ); - $db->commit(); - } catch ( DBQueryError $e ) { - $this->handleWriteError( $e ); - } - } - - /** - * Serialize an object and, if possible, compress the representation. - * On typical message and page data, this can provide a 3X decrease - * in storage requirements. - * - * @param $data mixed - * @return string - */ - protected function serialize( &$data ) { - $serial = serialize( $data ); - - if ( function_exists( 'gzdeflate' ) ) { - return gzdeflate( $serial ); - } else { - return $serial; - } - } - - /** - * Unserialize and, if necessary, decompress an object. - * @param $serial string - * @return mixed - */ - protected function unserialize( $serial ) { - if ( function_exists( 'gzinflate' ) ) { - $decomp = @gzinflate( $serial ); - - if ( false !== $decomp ) { - $serial = $decomp; - } - } - - $ret = unserialize( $serial ); - - return $ret; - } - - /** - * Handle a DBQueryError which occurred during a write operation. - * Ignore errors which are due to a read-only database, rethrow others. - */ - protected function handleWriteError( $exception ) { - $db = $this->getDB(); - - if ( !$db->wasReadOnlyError() ) { - throw $exception; - } - - 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 APC's shared memory functions - * - * @ingroup Cache - */ -class APCBagOStuff extends BagOStuff { - public function get( $key ) { - $val = apc_fetch( $key ); - - if ( is_string( $val ) ) { - $val = unserialize( $val ); - } - - return $val; - } - - public function set( $key, $value, $exptime = 0 ) { - apc_store( $key, serialize( $value ), $exptime ); - - return true; - } - - public function delete( $key, $time = 0 ) { - apc_delete( $key ); - - return true; - } - - public function keys() { - $info = apc_cache_info( 'user' ); - $list = $info['cache_list']; - $keys = array(); - - foreach ( $list as $entry ) { - $keys[] = $entry['info']; - } - - return $keys; - } -} - -/** - * This is a wrapper for eAccelerator's shared memory functions. - * - * This is basically identical to the deceased Turck MMCache version, - * mostly because eAccelerator is based on Turck MMCache. - * - * @ingroup Cache - */ -class eAccelBagOStuff extends BagOStuff { - public function get( $key ) { - $val = eaccelerator_get( $key ); - - if ( is_string( $val ) ) { - $val = unserialize( $val ); - } - - return $val; - } - - public function set( $key, $value, $exptime = 0 ) { - eaccelerator_put( $key, serialize( $value ), $exptime ); - - return true; - } - - public function delete( $key, $time = 0 ) { - eaccelerator_rm( $key ); - - return true; - } - - public function lock( $key, $waitTimeout = 0 ) { - eaccelerator_lock( $key ); - - return true; - } - - public function unlock( $key ) { - eaccelerator_unlock( $key ); - - return true; - } -} - -/** - * Wrapper for XCache object caching functions; identical interface - * to the APC wrapper - * - * @ingroup Cache - */ -class XCacheBagOStuff extends BagOStuff { - /** - * Get a value from the XCache object cache - * - * @param $key String: cache key - * @return mixed - */ - public function get( $key ) { - $val = xcache_get( $key ); - - if ( is_string( $val ) ) { - $val = unserialize( $val ); - } - - return $val; - } - - /** - * Store a value in the XCache object cache - * - * @param $key String: cache key - * @param $value Mixed: object to store - * @param $expire Int: expiration time - * @return bool - */ - public function set( $key, $value, $expire = 0 ) { - xcache_set( $key, serialize( $value ), $expire ); - - return true; - } - - /** - * Remove a value from the XCache object cache - * - * @param $key String: cache key - * @param $time Int: not used in this implementation - * @return bool - */ - public function delete( $key, $time = 0 ) { - xcache_unset( $key ); - - return true; - } -} - -/** - * 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; - - public function __construct( $dir = false ) { - global $wgDBAhandler; - - if ( $dir === false ) { - global $wgTmpDirectory; - $dir = $wgTmpDirectory; - } - - $this->mFile = "$dir/mw-cache-" . wfWikiID(); - $this->mFile .= '.db'; - wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" ); - $this->mHandler = $wgDBAhandler; - } - - /** - * Encode value and expiry for storage - */ - function encode( $value, $expiry ) { - # Convert to absolute time - $expiry = $this->convertExpiry( $expiry ); - - return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value ); - } - - /** - * @return list containing value first and expiry second - */ - function decode( $blob ) { - if ( !is_string( $blob ) ) { - return array( null, 0 ); - } else { - return array( - unserialize( substr( $blob, 11 ) ), - intval( substr( $blob, 0, 10 ) ) - ); - } - } - - function getReader() { - if ( file_exists( $this->mFile ) ) { - $handle = dba_open( $this->mFile, 'rl', $this->mHandler ); - } else { - $handle = $this->getWriter(); - } - - if ( !$handle ) { - wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); - } - - return $handle; - } - - function getWriter() { - $handle = dba_open( $this->mFile, 'cl', $this->mHandler ); - - if ( !$handle ) { - wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); - } - - return $handle; - } - - function get( $key ) { - wfProfileIn( __METHOD__ ); - wfDebug( __METHOD__ . "($key)\n" ); - - $handle = $this->getReader(); - if ( !$handle ) { - wfProfileOut( __METHOD__ ); - return null; - } - - $val = dba_fetch( $key, $handle ); - list( $val, $expiry ) = $this->decode( $val ); - - # Must close ASAP because locks are held - dba_close( $handle ); - - if ( !is_null( $val ) && $expiry && $expiry < time() ) { - # Key is expired, delete it - $handle = $this->getWriter(); - dba_delete( $key, $handle ); - dba_close( $handle ); - wfDebug( __METHOD__ . ": $key expired\n" ); - $val = null; - } - - wfProfileOut( __METHOD__ ); - return $val; - } - - function set( $key, $value, $exptime = 0 ) { - wfProfileIn( __METHOD__ ); - wfDebug( __METHOD__ . "($key)\n" ); - - $blob = $this->encode( $value, $exptime ); - - $handle = $this->getWriter(); - if ( !$handle ) { - wfProfileOut( __METHOD__ ); - return false; - } - - $ret = dba_replace( $key, $blob, $handle ); - dba_close( $handle ); - - wfProfileOut( __METHOD__ ); - return $ret; - } - - function delete( $key, $time = 0 ) { - wfProfileIn( __METHOD__ ); - wfDebug( __METHOD__ . "($key)\n" ); - - $handle = $this->getWriter(); - if ( !$handle ) { - wfProfileOut( __METHOD__ ); - return false; - } - - $ret = dba_delete( $key, $handle ); - dba_close( $handle ); - - wfProfileOut( __METHOD__ ); - return $ret; - } - - function add( $key, $value, $exptime = 0 ) { - wfProfileIn( __METHOD__ ); - - $blob = $this->encode( $value, $exptime ); - - $handle = $this->getWriter(); - - if ( !$handle ) { - wfProfileOut( __METHOD__ ); - return false; - } - - $ret = dba_insert( $key, $blob, $handle ); - - # Insert failed, check to see if it failed due to an expired key - if ( !$ret ) { - list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) ); - - if ( $expiry < time() ) { - # Yes expired, delete and try again - dba_delete( $key, $handle ); - $ret = dba_insert( $key, $blob, $handle ); - # This time if it failed then it will be handled by the caller like any other race - } - } - - dba_close( $handle ); - - wfProfileOut( __METHOD__ ); - return $ret; - } - - function keys() { - $reader = $this->getReader(); - $k1 = dba_firstkey( $reader ); - - if ( !$k1 ) { - return array(); - } - - $result[] = $k1; - - while ( $key = dba_nextkey( $reader ) ) { - $result[] = $key; - } - - return $result; - } -} - -/** - * Wrapper for WinCache object caching functions; identical interface - * to the APC wrapper - * - * @ingroup Cache - */ -class WinCacheBagOStuff extends BagOStuff { - - /** - * Get a value from the WinCache object cache - * - * @param $key String: cache key - * @return mixed - */ - public function get( $key ) { - $val = wincache_ucache_get( $key ); - - if ( is_string( $val ) ) { - $val = unserialize( $val ); - } - - return $val; - } - - /** - * Store a value in the WinCache object cache - * - * @param $key String: cache key - * @param $value Mixed: object to store - * @param $expire Int: expiration time - * @return bool - */ - public function set( $key, $value, $expire = 0 ) { - $result = wincache_ucache_set( $key, serialize( $value ), $expire ); - - /* wincache_ucache_set returns an empty array on success if $value - was an array, bool otherwise */ - return ( is_array( $result ) && $result === array() ) || $result; - } - - /** - * Remove a value from the WinCache object cache - * - * @param $key String: cache key - * @param $time Int: not used in this implementation - * @return bool - */ - public function delete( $key, $time = 0 ) { - wincache_ucache_delete( $key ); - - return true; - } - - public function keys() { - $info = wincache_ucache_info(); - $list = $info['ucache_entries']; - $keys = array(); - - if ( is_null( $list ) ) { - return array(); - } - - foreach ( $list as $entry ) { - $keys[] = $entry['key_name']; - } - - return $keys; - } -} diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php deleted file mode 100644 index 6824d21e74..0000000000 --- a/includes/ObjectCache.php +++ /dev/null @@ -1,125 +0,0 @@ - $wgMemCachedPersistent, 'compress_threshold' => 1500 ) ); - $wgCaches[CACHE_MEMCACHED]->set_servers( $wgMemCachedServers ); - $wgCaches[CACHE_MEMCACHED]->set_debug( $wgMemCachedDebug ); - } - $cache =& $wgCaches[CACHE_MEMCACHED]; - } elseif ( $type == CACHE_ACCEL ) { - if ( !array_key_exists( CACHE_ACCEL, $wgCaches ) ) { - if ( function_exists( 'eaccelerator_get' ) ) { - $wgCaches[CACHE_ACCEL] = new eAccelBagOStuff; - } elseif ( function_exists( 'apc_fetch') ) { - $wgCaches[CACHE_ACCEL] = new APCBagOStuff; - } elseif( function_exists( 'xcache_get' ) ) { - $wgCaches[CACHE_ACCEL] = new XCacheBagOStuff(); - } elseif( function_exists( 'wincache_ucache_get' ) ) { - $wgCaches[CACHE_ACCEL] = new WinCacheBagOStuff(); - } else { - $wgCaches[CACHE_ACCEL] = false; - } - } - if ( $wgCaches[CACHE_ACCEL] !== false ) { - $cache =& $wgCaches[CACHE_ACCEL]; - } - } elseif ( $type == CACHE_DBA ) { - if ( !array_key_exists( CACHE_DBA, $wgCaches ) ) { - $wgCaches[CACHE_DBA] = new DBABagOStuff; - } - $cache =& $wgCaches[CACHE_DBA]; - } - - if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) { - if ( !array_key_exists( CACHE_DB, $wgCaches ) ) { - $wgCaches[CACHE_DB] = new SqlBagOStuff('objectcache'); - } - $cache =& $wgCaches[CACHE_DB]; - } - - if ( $cache === false ) { - if ( !array_key_exists( CACHE_NONE, $wgCaches ) ) { - $wgCaches[CACHE_NONE] = new FakeMemCachedClient; - } - $cache =& $wgCaches[CACHE_NONE]; - } - - return $cache; -} - -/** Get the main cache object */ -function &wfGetMainCache() { - global $wgMainCacheType; - $ret =& wfGetCache( $wgMainCacheType ); - return $ret; -} - -/** Get the cache object used by the message cache */ -function &wfGetMessageCacheStorage() { - global $wgMessageCacheType; - $ret =& wfGetCache( $wgMessageCacheType ); - return $ret; -} - -/** Get the cache object used by the parser cache */ -function &wfGetParserCacheStorage() { - global $wgParserCacheType; - $ret =& wfGetCache( $wgParserCacheType ); - return $ret; -} diff --git a/includes/Setup.php b/includes/Setup.php index fac2745c55..f331dc4951 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -283,7 +283,7 @@ require_once( "$IP/includes/GlobalFunctions.php" ); require_once( "$IP/includes/Hooks.php" ); require_once( "$IP/includes/Namespace.php" ); require_once( "$IP/includes/ProxyTools.php" ); -require_once( "$IP/includes/ObjectCache.php" ); +require_once( "$IP/includes/objectcache/ObjectCache.php" ); require_once( "$IP/includes/ImageFunctions.php" ); wfProfileOut( $fname . '-includes' ); wfProfileIn( $fname . '-misc1' ); diff --git a/includes/objectcache/APCBagOStuff.php b/includes/objectcache/APCBagOStuff.php new file mode 100644 index 0000000000..dd4a76e158 --- /dev/null +++ b/includes/objectcache/APCBagOStuff.php @@ -0,0 +1,43 @@ + + * http://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Cache + */ + +/** + * @defgroup Cache Cache + */ + +/** + * interface is intended to be more or less compatible with + * the PHP memcached client. + * + * backends for local hash array and SQL table included: + * + * $bag = new HashBagOStuff(); + * $bag = new SqlBagOStuff(); # connect to db first + * + * + * @ingroup Cache + */ +abstract class BagOStuff { + var $debugMode = false; + + public function set_debug( $bool ) { + $this->debugMode = $bool; + } + + /* *** THE GUTS OF THE OPERATION *** */ + /* Override these with functional things in subclasses */ + + /** + * Get an item with the given key. Returns false if it does not exist. + * @param $key string + */ + abstract public function get( $key ); + + /** + * 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 ); + + /* + * 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 ); + + public function lock( $key, $timeout = 0 ) { + /* stub */ + return true; + } + + public function unlock( $key ) { + /* stub */ + return true; + } + + public function keys() { + /* stub */ + return array(); + } + + /* *** Emulated functions *** */ + /* Better performance can likely be got with custom written versions */ + public function get_multi( $keys ) { + $out = array(); + + foreach ( $keys as $key ) { + $out[$key] = $this->get( $key ); + } + + return $out; + } + + public function set_multi( $hash, $exptime = 0 ) { + foreach ( $hash as $key => $value ) { + $this->set( $key, $value, $exptime ); + } + } + + public function add( $key, $value, $exptime = 0 ) { + if ( !$this->get( $key ) ) { + $this->set( $key, $value, $exptime ); + + return true; + } + } + + public function add_multi( $hash, $exptime = 0 ) { + foreach ( $hash as $key => $value ) { + $this->add( $key, $value, $exptime ); + } + } + + public function delete_multi( $keys, $time = 0 ) { + foreach ( $keys as $key ) { + $this->delete( $key, $time ); + } + } + + public function replace( $key, $value, $exptime = 0 ) { + if ( $this->get( $key ) !== false ) { + $this->set( $key, $value, $exptime ); + } + } + + /** + * @param $key String: Key to increase + * @param $value Integer: Value to add to $key (Default 1) + * @return null if lock is not possible else $key value increased by $value + */ + public function incr( $key, $value = 1 ) { + if ( !$this->lock( $key ) ) { + return null; + } + + $value = intval( $value ); + + if ( ( $n = $this->get( $key ) ) !== false ) { + $n += $value; + $this->set( $key, $n ); // exptime? + } + $this->unlock( $key ); + + return $n; + } + + public function decr( $key, $value = 1 ) { + return $this->incr( $key, - $value ); + } + + public function debug( $text ) { + if ( $this->debugMode ) { + wfDebug( "BagOStuff debug: $text\n" ); + } + } + + /** + * Convert an optionally relative time to an absolute time + */ + protected function convertExpiry( $exptime ) { + if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) { + return time() + $exptime; + } else { + return $exptime; + } + } +} + + diff --git a/includes/objectcache/DBABagOStuff.php b/includes/objectcache/DBABagOStuff.php new file mode 100644 index 0000000000..2c07172cde --- /dev/null +++ b/includes/objectcache/DBABagOStuff.php @@ -0,0 +1,190 @@ +mFile = "$dir/mw-cache-" . wfWikiID(); + $this->mFile .= '.db'; + wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" ); + $this->mHandler = $wgDBAhandler; + } + + /** + * Encode value and expiry for storage + */ + function encode( $value, $expiry ) { + # Convert to absolute time + $expiry = $this->convertExpiry( $expiry ); + + return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value ); + } + + /** + * @return list containing value first and expiry second + */ + function decode( $blob ) { + if ( !is_string( $blob ) ) { + return array( null, 0 ); + } else { + return array( + unserialize( substr( $blob, 11 ) ), + intval( substr( $blob, 0, 10 ) ) + ); + } + } + + function getReader() { + if ( file_exists( $this->mFile ) ) { + $handle = dba_open( $this->mFile, 'rl', $this->mHandler ); + } else { + $handle = $this->getWriter(); + } + + if ( !$handle ) { + wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); + } + + return $handle; + } + + function getWriter() { + $handle = dba_open( $this->mFile, 'cl', $this->mHandler ); + + if ( !$handle ) { + wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); + } + + return $handle; + } + + function get( $key ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $handle = $this->getReader(); + if ( !$handle ) { + wfProfileOut( __METHOD__ ); + return null; + } + + $val = dba_fetch( $key, $handle ); + list( $val, $expiry ) = $this->decode( $val ); + + # Must close ASAP because locks are held + dba_close( $handle ); + + if ( !is_null( $val ) && $expiry && $expiry < time() ) { + # Key is expired, delete it + $handle = $this->getWriter(); + dba_delete( $key, $handle ); + dba_close( $handle ); + wfDebug( __METHOD__ . ": $key expired\n" ); + $val = null; + } + + wfProfileOut( __METHOD__ ); + return $val; + } + + function set( $key, $value, $exptime = 0 ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $blob = $this->encode( $value, $exptime ); + + $handle = $this->getWriter(); + if ( !$handle ) { + wfProfileOut( __METHOD__ ); + return false; + } + + $ret = dba_replace( $key, $blob, $handle ); + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function delete( $key, $time = 0 ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $handle = $this->getWriter(); + if ( !$handle ) { + wfProfileOut( __METHOD__ ); + return false; + } + + $ret = dba_delete( $key, $handle ); + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function add( $key, $value, $exptime = 0 ) { + wfProfileIn( __METHOD__ ); + + $blob = $this->encode( $value, $exptime ); + + $handle = $this->getWriter(); + + if ( !$handle ) { + wfProfileOut( __METHOD__ ); + return false; + } + + $ret = dba_insert( $key, $blob, $handle ); + + # Insert failed, check to see if it failed due to an expired key + if ( !$ret ) { + list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) ); + + if ( $expiry < time() ) { + # Yes expired, delete and try again + dba_delete( $key, $handle ); + $ret = dba_insert( $key, $blob, $handle ); + # This time if it failed then it will be handled by the caller like any other race + } + } + + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function keys() { + $reader = $this->getReader(); + $k1 = dba_firstkey( $reader ); + + if ( !$k1 ) { + return array(); + } + + $result[] = $k1; + + while ( $key = dba_nextkey( $reader ) ) { + $result[] = $key; + } + + return $result; + } +} + diff --git a/includes/objectcache/HashBagOStuff.php b/includes/objectcache/HashBagOStuff.php new file mode 100644 index 0000000000..36773306ce --- /dev/null +++ b/includes/objectcache/HashBagOStuff.php @@ -0,0 +1,58 @@ +bag = array(); + } + + protected function expire( $key ) { + $et = $this->bag[$key][1]; + + if ( ( $et == 0 ) || ( $et > time() ) ) { + return false; + } + + $this->delete( $key ); + + return true; + } + + function get( $key ) { + if ( !isset( $this->bag[$key] ) ) { + return false; + } + + if ( $this->expire( $key ) ) { + return false; + } + + return $this->bag[$key][0]; + } + + function set( $key, $value, $exptime = 0 ) { + $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) ); + } + + function delete( $key, $time = 0 ) { + if ( !isset( $this->bag[$key] ) ) { + return false; + } + + unset( $this->bag[$key] ); + + return true; + } + + function keys() { + return array_keys( $this->bag ); + } +} + diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php new file mode 100644 index 0000000000..6824d21e74 --- /dev/null +++ b/includes/objectcache/ObjectCache.php @@ -0,0 +1,125 @@ + $wgMemCachedPersistent, 'compress_threshold' => 1500 ) ); + $wgCaches[CACHE_MEMCACHED]->set_servers( $wgMemCachedServers ); + $wgCaches[CACHE_MEMCACHED]->set_debug( $wgMemCachedDebug ); + } + $cache =& $wgCaches[CACHE_MEMCACHED]; + } elseif ( $type == CACHE_ACCEL ) { + if ( !array_key_exists( CACHE_ACCEL, $wgCaches ) ) { + if ( function_exists( 'eaccelerator_get' ) ) { + $wgCaches[CACHE_ACCEL] = new eAccelBagOStuff; + } elseif ( function_exists( 'apc_fetch') ) { + $wgCaches[CACHE_ACCEL] = new APCBagOStuff; + } elseif( function_exists( 'xcache_get' ) ) { + $wgCaches[CACHE_ACCEL] = new XCacheBagOStuff(); + } elseif( function_exists( 'wincache_ucache_get' ) ) { + $wgCaches[CACHE_ACCEL] = new WinCacheBagOStuff(); + } else { + $wgCaches[CACHE_ACCEL] = false; + } + } + if ( $wgCaches[CACHE_ACCEL] !== false ) { + $cache =& $wgCaches[CACHE_ACCEL]; + } + } elseif ( $type == CACHE_DBA ) { + if ( !array_key_exists( CACHE_DBA, $wgCaches ) ) { + $wgCaches[CACHE_DBA] = new DBABagOStuff; + } + $cache =& $wgCaches[CACHE_DBA]; + } + + if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) { + if ( !array_key_exists( CACHE_DB, $wgCaches ) ) { + $wgCaches[CACHE_DB] = new SqlBagOStuff('objectcache'); + } + $cache =& $wgCaches[CACHE_DB]; + } + + if ( $cache === false ) { + if ( !array_key_exists( CACHE_NONE, $wgCaches ) ) { + $wgCaches[CACHE_NONE] = new FakeMemCachedClient; + } + $cache =& $wgCaches[CACHE_NONE]; + } + + return $cache; +} + +/** Get the main cache object */ +function &wfGetMainCache() { + global $wgMainCacheType; + $ret =& wfGetCache( $wgMainCacheType ); + return $ret; +} + +/** Get the cache object used by the message cache */ +function &wfGetMessageCacheStorage() { + global $wgMessageCacheType; + $ret =& wfGetCache( $wgMessageCacheType ); + return $ret; +} + +/** Get the cache object used by the parser cache */ +function &wfGetParserCacheStorage() { + global $wgParserCacheType; + $ret =& wfGetCache( $wgParserCacheType ); + return $ret; +} diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php new file mode 100644 index 0000000000..e58ab51c0a --- /dev/null +++ b/includes/objectcache/SqlBagOStuff.php @@ -0,0 +1,282 @@ +db ) ) { + /* We must keep a separate connection to MySQL in order to avoid deadlocks + * However, SQLite has an opposite behaviour. + * @todo Investigate behaviour for other databases + */ + if ( wfGetDB( DB_MASTER )->getType() == 'sqlite' ) { + $this->db = wfGetDB( DB_MASTER ); + } else { + $this->lb = wfGetLBFactory()->newMainLB(); + $this->db = $this->lb->getConnection( DB_MASTER ); + $this->db->clearFlag( DBO_TRX ); + } + } + + return $this->db; + } + + public function get( $key ) { + # expire old entries if any + $this->garbageCollect(); + $db = $this->getDB(); + $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), + array( 'keyname' => $key ), __METHOD__ ); + + if ( !$row ) { + $this->debug( 'get: no matching rows' ); + 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 false; + } + + return $this->unserialize( $db->decodeBlob( $row->value ) ); + } + + 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 + $exptime += time(); + } + + $encExpiry = $db->timestamp( $exptime ); + } + try { + $db->begin(); + // (bug 24425) use a replace if the db supports it instead of + // delete/insert to avoid clashes with conflicting keynames + $db->replace( 'objectcache', array( 'keyname' ), + array( + 'keyname' => $key, + 'value' => $db->encodeBlob( $this->serialize( $value ) ), + 'exptime' => $encExpiry + ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + + return false; + } + + return true; + } + + 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; + } + + return true; + } + + 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 null; + } + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); + if ( $this->isExpired( $row->exptime ) ) { + // Expired, do not reinsert + $db->commit(); + + return null; + } + + $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 null; + } + + return $newValue; + } + + public function keys() { + $db = $this->getDB(); + $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ ); + $result = array(); + + foreach ( $res as $row ) { + $result[] = $row->keyname; + } + + return $result; + } + + protected function isExpired( $exptime ) { + return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time(); + } + + protected function getMaxDateTime() { + if ( time() > 0x7fffffff ) { + return $this->getDB()->timestamp( 1 << 62 ); + } else { + return $this->getDB()->timestamp( 0x7fffffff ); + } + } + + protected function garbageCollect() { + /* Ignore 99% of requests */ + if ( !mt_rand( 0, 100 ) ) { + $now = time(); + /* Avoid repeating the delete within a few seconds */ + if ( $now > ( $this->lastExpireAll + 1 ) ) { + $this->lastExpireAll = $now; + $this->expireAll(); + } + } + } + + 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 ); + } + } + + public function deleteAll() { + $db = $this->getDB(); + + try { + $db->begin(); + $db->delete( 'objectcache', '*', __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + } + } + + /** + * Serialize an object and, if possible, compress the representation. + * On typical message and page data, this can provide a 3X decrease + * in storage requirements. + * + * @param $data mixed + * @return string + */ + protected function serialize( &$data ) { + $serial = serialize( $data ); + + if ( function_exists( 'gzdeflate' ) ) { + return gzdeflate( $serial ); + } else { + return $serial; + } + } + + /** + * Unserialize and, if necessary, decompress an object. + * @param $serial string + * @return mixed + */ + protected function unserialize( $serial ) { + if ( function_exists( 'gzinflate' ) ) { + $decomp = @gzinflate( $serial ); + + if ( false !== $decomp ) { + $serial = $decomp; + } + } + + $ret = unserialize( $serial ); + + return $ret; + } + + /** + * Handle a DBQueryError which occurred during a write operation. + * Ignore errors which are due to a read-only database, rethrow others. + */ + protected function handleWriteError( $exception ) { + $db = $this->getDB(); + + if ( !$db->wasReadOnlyError() ) { + throw $exception; + } + + try { + $db->rollback(); + } catch ( DBQueryError $e ) { + } + + wfDebug( __METHOD__ . ": ignoring query error\n" ); + $db->ignoreErrors( false ); + } +} + +/** + * Backwards compatibility alias + */ +class MediaWikiBagOStuff extends SqlBagOStuff { } + diff --git a/includes/objectcache/WinCacheBagOStuff.php b/includes/objectcache/WinCacheBagOStuff.php new file mode 100644 index 0000000000..7f46494621 --- /dev/null +++ b/includes/objectcache/WinCacheBagOStuff.php @@ -0,0 +1,71 @@ +