123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 |
- <?php
- #
- # Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
- # 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
- /**
- * @defgroup Cache Cache
- *
- * @file
- * @ingroup Cache
- */
- /**
- * interface is intended to be more or less compatible with
- * the PHP memcached client.
- *
- * backends for local hash array and SQL table included:
- * <code>
- * $bag = new HashBagOStuff();
- * $bag = new MediaWikiBagOStuff($tablename); # connect to db first
- * </code>
- *
- * @ingroup Cache
- */
- class BagOStuff {
- var $debugmode;
- 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;
- }
- function set($key, $value, $exptime=0) {
- /* stub */
- return false;
- }
- function delete($key, $time=0) {
- /* stub */
- return false;
- }
- function lock($key, $timeout = 0) {
- /* stub */
- return true;
- }
- function unlock($key) {
- /* stub */
- return true;
- }
- function keys() {
- /* stub */
- return array();
- }
- /* *** Emulated functions *** */
- /* Better performance can likely be got with custom written versions */
- function get_multi($keys) {
- $out = array();
- 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);
- }
- 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);
- }
- 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);
- }
- function incr($key, $value=1) {
- if ( !$this->lock($key) ) {
- return false;
- }
- $value = intval($value);
- if($value < 0) $value = 0;
- $n = false;
- if( ($n = $this->get($key)) !== false ) {
- $n += $value;
- $this->set($key, $n); // exptime?
- }
- $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;
- }
- 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!
- * 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();
- }
- function _expire($key) {
- $et = $this->bag[$key][1];
- if(($et == 0) || ($et > time()))
- return false;
- $this->delete($key);
- return true;
- }
- function get($key) {
- if(!$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, BagOStuff::convertExpiry( $exptime ) );
- }
- function delete($key,$time=0) {
- if(!$this->bag[$key])
- return false;
- unset($this->bag[$key]);
- return true;
- }
- function keys() {
- return array_keys( $this->bag );
- }
- }
- /**
- * Generic class to store objects in a database
- *
- * @ingroup Cache
- */
- abstract class SqlBagOStuff extends BagOStuff {
- var $table;
- var $lastexpireall = 0;
- /**
- * 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) {
- $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
- 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;
- }
- 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 < 3.16e8) # ~10 years
- $exptime += time();
- $exp = $this->_fromunixtime($exptime);
- }
- $this->_begin();
- $this->_query(
- "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();
- // 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));
- }
- 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() {
- /* Ignore 99% of requests */
- if ( !mt_rand( 0, 100 ) ) {
- $nowtime = time();
- /* Avoid repeating the delete within a few seconds */
- if ( $nowtime > ($this->lastexpireall + 1) ) {
- $this->lastexpireall = $nowtime;
- $this->expireall();
- }
- }
- }
- function expireall() {
- /* Remove any items that have expired */
- if ( $this->_readonly() ) {
- return false;
- }
- $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;
- }
- $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 $data mixed
- * @return string
- */
- 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
- */
- function _unserialize( $serial ) {
- if( function_exists( 'gzinflate' ) ) {
- $decomp = @gzinflate( $serial );
- 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).
- */
- 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;
- }
- return $this->table;
- }
- }
- /**
- * 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
- * to use it for arrays but not for objects.
- *
- * @ingroup Cache
- */
- class TurckBagOStuff extends BagOStuff {
- function get($key) {
- $val = mmcache_get( $key );
- if ( is_string( $val ) ) {
- $val = unserialize( $val );
- }
- return $val;
- }
- function set($key, $value, $exptime=0) {
- mmcache_put( $key, serialize( $value ), $exptime );
- return true;
- }
- function delete($key, $time=0) {
- mmcache_rm( $key );
- return true;
- }
- function lock($key, $waitTimeout = 0 ) {
- mmcache_lock( $key );
- return true;
- }
- function unlock($key) {
- 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 basically identical to the Turck MMCache version,
- * mostly because eAccelerator is based on Turck MMCache.
- *
- * @ingroup Cache
- */
- class eAccelBagOStuff extends BagOStuff {
- function get($key) {
- $val = eaccelerator_get( $key );
- if ( is_string( $val ) ) {
- $val = unserialize( $val );
- }
- return $val;
- }
- function set($key, $value, $exptime=0) {
- eaccelerator_put( $key, serialize( $value ), $exptime );
- return true;
- }
- function delete($key, $time=0) {
- eaccelerator_rm( $key );
- return true;
- }
- function lock($key, $waitTimeout = 0 ) {
- eaccelerator_lock( $key );
- return true;
- }
- 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;
- }
- }
- /**
- * @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;
- }
- }
|