X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FBagOStuff.php;h=8f548c86f4bd7087938197f87dd5a237b2e97fed;hb=fd2900c8a45145a1ca9dc0e15d5033366c21b9d0;hp=9ac3064a6b83786700f34f21f46ab74a2f09826e;hpb=b0c41ad13b4919a2b5fd2c3ba269ef399d183e64;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php index 9ac3064a6b..8f548c86f4 100644 --- a/includes/BagOStuff.php +++ b/includes/BagOStuff.php @@ -2,53 +2,55 @@ # # Copyright (C) 2003-2004 Brion Vibber # 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 +# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html + /** + * @defgroup Cache Cache * - * @package MediaWiki + * @file + * @ingroup Cache */ - + /** - * Simple generic object store - * * 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 MysqlBagOStuff($tablename); # connect to db first + * + * $bag = new HashBagOStuff(); + * $bag = new MediaWikiBagOStuff($tablename); # connect to db first + * * - * @package MediaWiki - * @abstract + * @ingroup Cache */ class BagOStuff { var $debugmode; - - function BagOStuff() { + + function __construct() { $this->set_debug( false ); } - + 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; @@ -58,12 +60,12 @@ class BagOStuff { /* stub */ return false; } - + function delete($key, $time=0) { /* stub */ return false; } - + function lock($key, $timeout = 0) { /* stub */ return true; @@ -73,7 +75,12 @@ class BagOStuff { /* stub */ return true; } - + + function keys() { + /* stub */ + return array(); + } + /* *** Emulated functions *** */ /* Better performance can likely be got with custom written versions */ function get_multi($keys) { @@ -82,19 +89,19 @@ class BagOStuff { $out[$key] = $this->get($key); return $out; } - + 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); return true; } } - + function add_multi($hash, $exptime=0) { foreach($hash as $key => $value) $this->add($key, $value, $exptime); @@ -104,12 +111,12 @@ class BagOStuff { foreach($keys as $key) $this->delete($key, $time); } - + 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) ) { return false; @@ -125,7 +132,7 @@ class BagOStuff { $this->unlock($key); return $n; } - + function decr($key, $value=1) { if ( !$this->lock($key) ) { return false; @@ -142,31 +149,39 @@ class BagOStuff { $this->unlock($key); return $m; } - + 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)) { + return time() + $exptime; + } else { + return $exptime; + } + } } /** * Functional versions! - * @todo document - * @package MediaWiki + * 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 { - /* - This is a test of the interface, mainly. It stores - things in an associative array, which is not going to - persist between program runs. - */ var $bag; - - function HashBagOStuff() { + + function __construct() { $this->bag = array(); } - + function _expire($key) { $et = $this->bag[$key][1]; if(($et == 0) || ($et > time())) @@ -174,56 +189,53 @@ class HashBagOStuff extends BagOStuff { $this->delete($key); return true; } - + function get($key) { - if(!$this->bag[$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) { - if(($exptime != 0) && ($exptime < 3600*24*30)) - $exptime = time() + $exptime; - $this->bag[$key] = array( $value, $exptime ); + $this->bag[$key] = array( $value, BagOStuff::convertExpiry( $exptime ) ); } - + function delete($key,$time=0) { - if(!$this->bag[$key]) + if( !isset( $this->bag[$key] ) ) return false; unset($this->bag[$key]); return true; } -} -/* -CREATE TABLE objectcache ( - keyname char(255) binary not null default '', - value mediumblob, - exptime datetime, - unique key (keyname), - key (exptime) -); -*/ + function keys() { + return array_keys( $this->bag ); + } +} /** - * @todo document - * @abstract - * @package MediaWiki + * Generic class to store objects in a database + * + * @ingroup Cache */ -class SqlBagOStuff extends BagOStuff { +abstract class SqlBagOStuff extends BagOStuff { var $table; var $lastexpireall = 0; - function SqlBagOStuff($tablename = 'objectcache') { + /** + * Constructor + * + * @param $tablename String: name of the table to use + */ + function __construct($tablename = 'objectcache') { $this->table = $tablename; } - + function get($key) { /* expire old entries if any */ $this->garbageCollect(); - + $res = $this->_query( "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key); if(!$res) { @@ -232,40 +244,73 @@ class SqlBagOStuff extends BagOStuff { } if($row=$this->_fetchobject($res)) { $this->_debug("get: retrieved data; exp time is " . $row->exptime); - return $this->_unserialize($row->value); + if ( $row->exptime != $this->_maxdatetime() && + wfTimestamp( TS_UNIX, $row->exptime ) < time() ) + { + $this->_debug("get: key has expired, deleting"); + $this->delete($key); + return false; + } + return $this->_unserialize($this->_blobdecode($row->value)); } else { $this->_debug('get: no matching rows'); } return false; } - + 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(); } else { - if($exptime < 3600*24*30) + if($exptime < 3.16e8) # ~10 years $exptime += time(); $exp = $this->_fromunixtime($exptime); } - $this->delete( $key ); + $this->_begin(); $this->_query( - "INSERT INTO $0 (keyname,value,exptime) VALUES('$1','$2','$exp')", - $key, $this->_serialize($value)); + "DELETE FROM $0 WHERE keyname='$1'", $key ); + $this->_doinsert($this->getTableName(), array( + 'keyname' => $key, + 'value' => $this->_blobencode($this->_serialize($value)), + 'exptime' => $exp + )); + $this->_commit(); return true; /* ? */ } - + function delete($key,$time=0) { + if ( $this->_readonly() ) { + return false; + } + $this->_begin(); $this->_query( "DELETE FROM $0 WHERE keyname='$1'", $key ); + $this->_commit(); return true; /* ? */ } - + + function keys() { + $res = $this->_query( "SELECT keyname FROM $0" ); + if(!$res) { + $this->_debug("keys: ** error: " . $this->_dberror($res) . " **"); + return array(); + } + $result = array(); + while( $row = $this->_fetchobject($res) ) { + $result[] = $row->keyname; + } + return $result; + } + function getTableName() { return $this->table; } - + function _query($sql) { $reps = func_get_args(); $reps[0] = $this->getTableName(); @@ -273,7 +318,7 @@ class SqlBagOStuff extends BagOStuff { for($i=0;$i_strencode($reps[$i]), + $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i], $sql); } $res = $this->_doquery($sql); @@ -282,38 +327,39 @@ class SqlBagOStuff extends BagOStuff { } return $res; } - + function _strencode($str) { /* Protect strings in SQL */ return str_replace( "'", "''", $str ); } - - function _doquery($sql) { - die( 'abstract function SqlBagOStuff::_doquery() must be defined' ); + function _blobencode($str) { + return $str; } - - function _fetchrow($res) { - die( 'abstract function SqlBagOStuff::_fetchrow() must be defined' ); + 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'; } - - function _maxdatetime() { - die( 'abstract function SqlBagOStuff::_maxdatetime() must be defined' ); - } - - function _fromunixtime() { - die( 'abstract function SqlBagOStuff::_fromunixtime() must be defined' ); - } - + + abstract function _maxdatetime(); + abstract function _fromunixtime($ts); + function garbageCollect() { /* Ignore 99% of requests */ if ( !mt_rand( 0, 100 ) ) { @@ -325,24 +371,34 @@ class SqlBagOStuff extends BagOStuff { } } } - + function expireall() { /* Remove any items that have expired */ + if ( $this->_readonly() ) { + return false; + } $now = $this->_fromunixtime( time() ); - $this->_query( "DELETE FROM $0 WHERE exptime<'$now'" ); + $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; + } + $this->_begin(); $this->_query( "DELETE FROM $0" ); + $this->_commit(); } - + /** * 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 mixed $data + * @param $data mixed * @return string */ function _serialize( &$data ) { @@ -353,10 +409,10 @@ class SqlBagOStuff extends BagOStuff { return $serial; } } - + /** * Unserialize and, if necessary, decompress an object. - * @param string $serial + * @param $serial string * @return mixed */ function _unserialize( $serial ) { @@ -366,46 +422,89 @@ class SqlBagOStuff extends BagOStuff { $serial = $decomp; } } - return unserialize( $serial ); + $ret = unserialize( $serial ); + return $ret; } } /** - * @todo document - * @package MediaWiki + * 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) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->query($sql, 'MediaWikiBagOStuff:_doquery'); + return $this->_getDB()->query( $sql, __METHOD__ ); + } + function _doinsert($t, $v) { + return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) ); } function _fetchobject($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->fetchObject($result); + return $this->_getDB()->fetchObject($result); } function _freeresult($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->freeResult($result); + return $this->_getDB()->freeResult($result); } function _dberror($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->lastError(); + return $this->_getDB()->lastError(); } function _maxdatetime() { - return '9999-12-31 12:59:59'; + if ( time() > 0x7fffffff ) { + return $this->_fromunixtime( 1<<62 ); + } else { + return $this->_fromunixtime( 0x7fffffff ); + } } function _fromunixtime($ts) { - return gmdate( 'Y-m-d H:i:s', $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). + */ + function _readonly(){ + return false; } function _strencode($s) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->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 =& wfGetDB( DB_MASTER ); + $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; } @@ -414,19 +513,19 @@ class MediaWikiBagOStuff extends SqlBagOStuff { } /** - * This is a wrapper for Turck MMCache's shared memory functions. - * - * You can store objects with mmcache_put() and mmcache_get(), but Turck seems - * to use a weird custom serializer that randomly segfaults. So we wrap calls + * This is a wrapper for Turck MMCache's shared memory functions. + * + * You can store objects with mmcache_put() and mmcache_get(), but Turck seems + * to use a weird custom serializer that randomly segfaults. So we wrap calls * with serialize()/unserialize(). - * + * * The thing I noticed about the Turck serialized data was that unlike ordinary - * serialize(), it contained the names of methods, and judging by the amount of - * binary data, perhaps even the bytecode of the methods themselves. It may be - * that Turck's serializer is faster, so a possible future extension would be + * serialize(), it contained the names of methods, and judging by the amount of + * binary data, perhaps even the bytecode of the methods themselves. It may be + * that Turck's serializer is faster, so a possible future extension would be * to use it for arrays but not for objects. * - * @package MediaWiki + * @ingroup Cache */ class TurckBagOStuff extends BagOStuff { function get($key) { @@ -441,7 +540,7 @@ class TurckBagOStuff extends BagOStuff { mmcache_put( $key, serialize( $value ), $exptime ); return true; } - + function delete($key, $time=0) { mmcache_rm( $key ); return true; @@ -456,15 +555,41 @@ class TurckBagOStuff extends BagOStuff { mmcache_unlock( $key ); return true; } -} +} + +/** + * This is a wrapper for APC's shared memory functions + * + * @ingroup Cache + */ +class APCBagOStuff extends BagOStuff { + 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); + return true; + } + + function delete($key, $time=0) { + apc_delete($key); + return true; + } +} + /** - * This is a wrapper for eAccelerator's shared memory functions. - * + * This is a wrapper for eAccelerator's shared memory functions. + * * This is basically identical to the Turck MMCache version, * mostly because eAccelerator is based on Turck MMCache. * - * @package MediaWiki + * @ingroup Cache */ class eAccelBagOStuff extends BagOStuff { function get($key) { @@ -479,7 +604,7 @@ class eAccelBagOStuff extends BagOStuff { eaccelerator_put( $key, serialize( $value ), $exptime ); return true; } - + function delete($key, $time=0) { eaccelerator_rm( $key ); return true; @@ -494,5 +619,202 @@ class eAccelBagOStuff extends BagOStuff { 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; + } + +} + +/** + * @todo document + * @ingroup Cache + */ +class DBABagOStuff extends BagOStuff { + var $mHandler, $mFile, $mReader, $mWriter, $mDisabled; + + 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" ); + $this->mHandler = $handler; + } + + /** + * Encode value and expiry for storage + */ + function encode( $value, $expiry ) { + # Convert to absolute time + $expiry = BagOStuff::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 ) { + 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 ) { + 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 ) { + 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 ) { + 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; + } +}