Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F4455
BagOStuff.php
Public
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
•
bzimport
Nov 21 2014, 9:58 PM
2014-11-21 21:58:19 (UTC+0)
Size
17 KB
Referenced Files
None
Subscribers
None
BagOStuff.php
View Options
<?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
)
{
return
$this
->
set
(
$key
,
$value
,
$exptime
);
}
}
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
->
delete
(
$key
);
$this
->
_doinsert
(
$this
->
getTableName
(),
array
(
'keyname'
=>
$key
,
'value'
=>
$this
->
_blobencode
(
$this
->
_serialize
(
$value
)),
'exptime'
=>
$exp
));
return
true
;
/* ? */
}
function
delete
(
$key
,
$time
=
0
)
{
if
(
$this
->
_readonly
()
)
{
return
false
;
}
$this
->
_query
(
"DELETE FROM $0 WHERE keyname='$1'"
,
$key
);
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
_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
->
_query
(
"DELETE FROM $0 WHERE exptime < '$now'"
);
}
function
deleteall
()
{
/* Clear *all* items from cache table */
if
(
$this
->
_readonly
()
)
{
return
false
;
}
$this
->
_query
(
"DELETE FROM $0"
);
}
/**
* 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
;
function
_getDB
(){
static
$db
;
if
(
!
isset
(
$db
)
)
$db
=
wfGetDB
(
DB_MASTER
);
return
$db
;
}
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
);
}
function
_readonly
(){
return
wfReadOnly
();
}
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
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4020
Default Alt Text
BagOStuff.php (17 KB)
Attached To
Mode
T13533: MessageCache absurdly slow with CACHE_NONE with wgReadOnly mode enabled
Attached
Detach File
Event Timeline
Log In to Comment