2011-03-28 14:21:28 +00:00
|
|
|
<?
|
|
|
|
/*************************************************************************|
|
|
|
|
|--------------- Caching class -------------------------------------------|
|
|
|
|
|*************************************************************************|
|
|
|
|
|
|
|
|
This class is a wrapper for the Memcache class, and it's been written in
|
|
|
|
order to better handle the caching of full pages with bits of dynamic
|
|
|
|
content that are different for every user.
|
|
|
|
|
|
|
|
As this inherits memcache, all of the default memcache methods work -
|
|
|
|
however, this class has page caching functions superior to those of
|
|
|
|
memcache.
|
|
|
|
|
|
|
|
Also, Memcache::get and Memcache::set have been wrapped by
|
|
|
|
CACHE::get_value and CACHE::cache_value. get_value uses the same argument
|
|
|
|
as get, but cache_value only takes the key, the value, and the duration
|
|
|
|
(no zlib).
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
// Unix sockets
|
2011-03-28 14:21:28 +00:00
|
|
|
memcached -d -m 5120 -s /var/run/memcached.sock -a 0777 -t16 -C -u root
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
// TCP bind
|
2011-03-28 14:21:28 +00:00
|
|
|
memcached -d -m 8192 -l 10.10.0.1 -t8 -C
|
|
|
|
|
|
|
|
|*************************************************************************/
|
|
|
|
|
|
|
|
if (!extension_loaded('memcache')) {
|
2013-03-13 08:00:43 +00:00
|
|
|
die('Memcache Extension not loaded.');
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
class CACHE extends Memcache {
|
2013-02-18 08:00:22 +00:00
|
|
|
/**
|
|
|
|
* Torrent Group cache version
|
|
|
|
*/
|
2013-02-25 21:16:55 +00:00
|
|
|
const GROUP_VERSION = 5;
|
2013-02-18 08:00:22 +00:00
|
|
|
|
2011-03-28 14:21:28 +00:00
|
|
|
public $CacheHits = array();
|
|
|
|
public $MemcacheDBArray = array();
|
|
|
|
public $MemcacheDBKey = '';
|
|
|
|
protected $InTransaction = false;
|
|
|
|
public $Time = 0;
|
2013-10-06 08:01:04 +00:00
|
|
|
private $Servers = array();
|
2011-05-25 08:00:08 +00:00
|
|
|
private $PersistentKeys = array(
|
2013-08-28 23:08:41 +00:00
|
|
|
'ajax_requests_*',
|
|
|
|
'query_lock_*',
|
2011-05-25 08:00:08 +00:00
|
|
|
'stats_*',
|
2012-10-09 08:00:17 +00:00
|
|
|
'top10tor_*',
|
2012-10-27 08:00:09 +00:00
|
|
|
'top10votes_*',
|
2013-02-04 08:00:13 +00:00
|
|
|
'users_snatched_*',
|
2013-08-28 23:08:41 +00:00
|
|
|
|
|
|
|
// Cache-based features
|
|
|
|
'global_notification',
|
|
|
|
'notifications_one_reads_*',
|
2011-05-25 08:00:08 +00:00
|
|
|
);
|
2013-08-28 23:08:41 +00:00
|
|
|
private $ClearedKeys = array();
|
2012-10-11 08:00:15 +00:00
|
|
|
|
2011-03-28 14:21:28 +00:00
|
|
|
public $CanClear = false;
|
2012-03-31 08:00:23 +00:00
|
|
|
public $InternalCache = true;
|
2011-03-28 14:21:28 +00:00
|
|
|
|
2012-10-03 08:00:16 +00:00
|
|
|
function __construct($Servers) {
|
2013-10-06 08:01:04 +00:00
|
|
|
$this->Servers = $Servers;
|
2012-10-03 08:00:16 +00:00
|
|
|
foreach ($Servers as $Server) {
|
|
|
|
$this->addServer($Server['host'], $Server['port'], true, $Server['buckets']);
|
|
|
|
}
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------- Caching functions ----------//
|
|
|
|
|
|
|
|
// Allows us to set an expiration on otherwise perminantly cache'd values
|
|
|
|
// Useful for disabled users, locked threads, basically reducing ram usage
|
2013-05-06 08:00:32 +00:00
|
|
|
public function expire_value($Key, $Duration = 2592000) {
|
|
|
|
$StartTime = microtime(true);
|
2011-03-28 14:21:28 +00:00
|
|
|
$this->set($Key, $this->get($Key), $Duration);
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper for Memcache::set, with the zlib option removed and default duration of 30 days
|
2013-05-06 08:00:32 +00:00
|
|
|
public function cache_value($Key, $Value, $Duration = 2592000) {
|
|
|
|
$StartTime = microtime(true);
|
2011-03-28 14:21:28 +00:00
|
|
|
if (empty($Key)) {
|
|
|
|
trigger_error("Cache insert failed for empty key");
|
|
|
|
}
|
|
|
|
if (!$this->set($Key, $Value, 0, $Duration)) {
|
|
|
|
trigger_error("Cache insert failed for key $Key");
|
|
|
|
}
|
2014-05-15 08:00:27 +00:00
|
|
|
if ($this->InternalCache && array_key_exists($Key, $this->CacheHits)) {
|
2014-05-03 08:01:07 +00:00
|
|
|
$this->CacheHits[$Key] = $Value;
|
|
|
|
}
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2012-10-11 08:00:15 +00:00
|
|
|
// Wrapper for Memcache::add, with the zlib option removed and default duration of 30 days
|
2013-05-06 08:00:32 +00:00
|
|
|
public function add_value($Key, $Value, $Duration = 2592000) {
|
|
|
|
$StartTime = microtime(true);
|
|
|
|
$Added = $this->add($Key, $Value, 0, $Duration);
|
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2012-10-11 08:00:15 +00:00
|
|
|
return $Added;
|
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
public function replace_value($Key, $Value, $Duration = 2592000) {
|
|
|
|
$StartTime = microtime(true);
|
2011-03-28 14:21:28 +00:00
|
|
|
$this->replace($Key, $Value, false, $Duration);
|
2014-05-15 08:00:27 +00:00
|
|
|
if ($this->InternalCache && array_key_exists($Key, $this->CacheHits)) {
|
2014-05-03 08:01:07 +00:00
|
|
|
$this->CacheHits[$Key] = $Value;
|
|
|
|
}
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
public function get_value($Key, $NoCache = false) {
|
2013-04-30 18:18:07 +00:00
|
|
|
if (!$this->InternalCache) {
|
2012-03-31 08:00:23 +00:00
|
|
|
$NoCache = true;
|
|
|
|
}
|
2013-09-14 08:00:52 +00:00
|
|
|
$StartTime = microtime(true);
|
2011-03-28 14:21:28 +00:00
|
|
|
if (empty($Key)) {
|
2013-05-06 08:00:32 +00:00
|
|
|
trigger_error('Cache retrieval failed for empty key');
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2013-09-14 08:00:52 +00:00
|
|
|
if (!empty($_GET['clearcache']) && $this->CanClear && !isset($this->ClearedKeys[$Key]) && !Misc::in_array_partial($Key, $this->PersistentKeys)) {
|
2013-09-13 08:00:53 +00:00
|
|
|
if ($_GET['clearcache'] === '1') {
|
2013-05-06 08:00:32 +00:00
|
|
|
// Because check_perms() isn't true until LoggedUser is pulled from the cache, we have to remove the entries loaded before the LoggedUser data
|
|
|
|
// Because of this, not user cache data will require a secondary pageload following the clearcache to update
|
2011-03-28 14:21:28 +00:00
|
|
|
if (count($this->CacheHits) > 0) {
|
2011-05-25 08:00:08 +00:00
|
|
|
foreach (array_keys($this->CacheHits) as $HitKey) {
|
2013-08-28 23:08:41 +00:00
|
|
|
if (!isset($this->ClearedKeys[$HitKey]) && !Misc::in_array_partial($HitKey, $this->PersistentKeys)) {
|
2011-05-25 08:00:08 +00:00
|
|
|
$this->delete($HitKey);
|
|
|
|
unset($this->CacheHits[$HitKey]);
|
2013-08-28 23:08:41 +00:00
|
|
|
$this->ClearedKeys[$HitKey] = true;
|
2011-05-25 08:00:08 +00:00
|
|
|
}
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->delete($Key);
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
return false;
|
2013-09-13 08:00:53 +00:00
|
|
|
} elseif ($_GET['clearcache'] == $Key) {
|
|
|
|
$this->delete($Key);
|
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
|
|
|
return false;
|
|
|
|
} elseif (substr($_GET['clearcache'], -1) === '*') {
|
|
|
|
$Prefix = substr($_GET['clearcache'], 0, -1);
|
2013-09-14 08:00:52 +00:00
|
|
|
if ($Prefix === '' || $Prefix === substr($Key, 0, strlen($Prefix))) {
|
2013-09-13 08:00:53 +00:00
|
|
|
$this->delete($Key);
|
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
2013-09-14 08:00:52 +00:00
|
|
|
$this->ClearedKeys[$Key] = true;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
// For cases like the forums, if a key is already loaded, grab the existing pointer
|
2011-03-28 14:21:28 +00:00
|
|
|
if (isset($this->CacheHits[$Key]) && !$NoCache) {
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
return $this->CacheHits[$Key];
|
|
|
|
}
|
|
|
|
|
|
|
|
$Return = $this->get($Key);
|
2012-10-28 08:00:19 +00:00
|
|
|
if ($Return !== false) {
|
|
|
|
$this->CacheHits[$Key] = $NoCache ? null : $Return;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
return $Return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper for Memcache::delete. For a reason, see above.
|
|
|
|
public function delete_value($Key) {
|
2013-05-06 08:00:32 +00:00
|
|
|
$StartTime = microtime(true);
|
2011-03-28 14:21:28 +00:00
|
|
|
if (empty($Key)) {
|
2013-05-06 08:00:32 +00:00
|
|
|
trigger_error('Cache deletion failed for empty key');
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
if (!$this->delete($Key)) {
|
|
|
|
//trigger_error("Cache delete failed for key $Key");
|
|
|
|
}
|
2014-05-03 08:01:07 +00:00
|
|
|
unset($this->CacheHits[$Key]);
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2013-02-04 08:00:13 +00:00
|
|
|
}
|
2013-02-22 08:00:24 +00:00
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
public function increment_value($Key, $Value = 1) {
|
|
|
|
$StartTime = microtime(true);
|
2014-05-03 08:01:07 +00:00
|
|
|
$NewVal = $this->increment($Key, $Value);
|
|
|
|
if (isset($this->CacheHits[$Key])) {
|
|
|
|
$this->CacheHits[$Key] = $NewVal;
|
|
|
|
}
|
2013-05-06 08:00:32 +00:00
|
|
|
$this->Time += (microtime(true) - $StartTime) * 1000;
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------- memcachedb functions ----------//
|
|
|
|
|
|
|
|
public function begin_transaction($Key) {
|
|
|
|
$Value = $this->get($Key);
|
|
|
|
if (!is_array($Value)) {
|
|
|
|
$this->InTransaction = false;
|
|
|
|
$this->MemcacheDBKey = array();
|
|
|
|
$this->MemcacheDBKey = '';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$this->MemcacheDBArray = $Value;
|
|
|
|
$this->MemcacheDBKey = $Key;
|
|
|
|
$this->InTransaction = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function cancel_transaction() {
|
|
|
|
$this->InTransaction = false;
|
|
|
|
$this->MemcacheDBKey = array();
|
|
|
|
$this->MemcacheDBKey = '';
|
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
public function commit_transaction($Time = 2592000) {
|
2011-03-28 14:21:28 +00:00
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$this->cache_value($this->MemcacheDBKey, $this->MemcacheDBArray, $Time);
|
|
|
|
$this->InTransaction = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Updates multiple rows in an array
|
|
|
|
public function update_transaction($Rows, $Values) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$Array = $this->MemcacheDBArray;
|
|
|
|
if (is_array($Rows)) {
|
|
|
|
$i = 0;
|
|
|
|
$Keys = $Rows[0];
|
|
|
|
$Property = $Rows[1];
|
|
|
|
foreach ($Keys as $Row) {
|
|
|
|
$Array[$Row][$Property] = $Values[$i];
|
|
|
|
$i++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$Array[$Rows] = $Values;
|
|
|
|
}
|
|
|
|
$this->MemcacheDBArray = $Array;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Updates multiple values in a single row in an array
|
|
|
|
// $Values must be an associative array with key:value pairs like in the array we're updating
|
|
|
|
public function update_row($Row, $Values) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($Row === false) {
|
|
|
|
$UpdateArray = $this->MemcacheDBArray;
|
|
|
|
} else {
|
|
|
|
$UpdateArray = $this->MemcacheDBArray[$Row];
|
|
|
|
}
|
|
|
|
foreach ($Values as $Key => $Value) {
|
|
|
|
if (!array_key_exists($Key, $UpdateArray)) {
|
|
|
|
trigger_error('Bad transaction key ('.$Key.') for cache '.$this->MemcacheDBKey);
|
|
|
|
}
|
|
|
|
if ($Value === '+1') {
|
|
|
|
if (!is_number($UpdateArray[$Key])) {
|
|
|
|
trigger_error('Tried to increment non-number ('.$Key.') for cache '.$this->MemcacheDBKey);
|
|
|
|
}
|
|
|
|
++$UpdateArray[$Key]; // Increment value
|
|
|
|
} elseif ($Value === '-1') {
|
|
|
|
if (!is_number($UpdateArray[$Key])) {
|
|
|
|
trigger_error('Tried to decrement non-number ('.$Key.') for cache '.$this->MemcacheDBKey);
|
|
|
|
}
|
|
|
|
--$UpdateArray[$Key]; // Decrement value
|
|
|
|
} else {
|
|
|
|
$UpdateArray[$Key] = $Value; // Otherwise, just alter value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($Row === false) {
|
|
|
|
$this->MemcacheDBArray = $UpdateArray;
|
|
|
|
} else {
|
|
|
|
$this->MemcacheDBArray[$Row] = $UpdateArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increments multiple values in a single row in an array
|
|
|
|
// $Values must be an associative array with key:value pairs like in the array we're updating
|
|
|
|
public function increment_row($Row, $Values) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($Row === false) {
|
|
|
|
$UpdateArray = $this->MemcacheDBArray;
|
|
|
|
} else {
|
|
|
|
$UpdateArray = $this->MemcacheDBArray[$Row];
|
|
|
|
}
|
|
|
|
foreach ($Values as $Key => $Value) {
|
|
|
|
if (!array_key_exists($Key, $UpdateArray)) {
|
2013-05-06 08:00:32 +00:00
|
|
|
trigger_error("Bad transaction key ($Key) for cache ".$this->MemcacheDBKey);
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
if (!is_number($Value)) {
|
2013-05-06 08:00:32 +00:00
|
|
|
trigger_error("Tried to increment with non-number ($Key) for cache ".$this->MemcacheDBKey);
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
$UpdateArray[$Key] += $Value; // Increment value
|
|
|
|
}
|
|
|
|
if ($Row === false) {
|
|
|
|
$this->MemcacheDBArray = $UpdateArray;
|
|
|
|
} else {
|
|
|
|
$this->MemcacheDBArray[$Row] = $UpdateArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert a value at the beginning of the array
|
|
|
|
public function insert_front($Key, $Value) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($Key === '') {
|
|
|
|
array_unshift($this->MemcacheDBArray, $Value);
|
|
|
|
} else {
|
|
|
|
$this->MemcacheDBArray = array($Key=>$Value) + $this->MemcacheDBArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert a value at the end of the array
|
|
|
|
public function insert_back($Key, $Value) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($Key === '') {
|
|
|
|
array_push($this->MemcacheDBArray, $Value);
|
|
|
|
} else {
|
|
|
|
$this->MemcacheDBArray = $this->MemcacheDBArray + array($Key=>$Value);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function insert($Key, $Value) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($Key === '') {
|
|
|
|
$this->MemcacheDBArray[] = $Value;
|
|
|
|
} else {
|
|
|
|
$this->MemcacheDBArray[$Key] = $Value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function delete_row($Row) {
|
|
|
|
if (!$this->InTransaction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!isset($this->MemcacheDBArray[$Row])) {
|
2013-05-06 08:00:32 +00:00
|
|
|
trigger_error("Tried to delete non-existent row ($Row) for cache ".$this->MemcacheDBKey);
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
unset($this->MemcacheDBArray[$Row]);
|
|
|
|
}
|
|
|
|
|
2013-05-06 08:00:32 +00:00
|
|
|
public function update($Key, $Rows, $Values, $Time = 2592000) {
|
2011-03-28 14:21:28 +00:00
|
|
|
if (!$this->InTransaction) {
|
|
|
|
$this->begin_transaction($Key);
|
|
|
|
$this->update_transaction($Rows, $Values);
|
|
|
|
$this->commit_transaction($Time);
|
|
|
|
} else {
|
|
|
|
$this->update_transaction($Rows, $Values);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-11 08:00:15 +00:00
|
|
|
/**
|
|
|
|
* Tries to set a lock. Expiry time is one hour to avoid indefinite locks
|
|
|
|
*
|
|
|
|
* @param string $LockName name on the lock
|
|
|
|
* @return true if lock was acquired
|
|
|
|
*/
|
|
|
|
public function get_query_lock($LockName) {
|
|
|
|
return $this->add_value('query_lock_'.$LockName, 1, 3600);
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
|
|
|
|
2012-10-11 08:00:15 +00:00
|
|
|
/**
|
|
|
|
* Remove lock
|
|
|
|
*
|
|
|
|
* @param string $LockName name on the lock
|
|
|
|
*/
|
|
|
|
public function clear_query_lock($LockName) {
|
|
|
|
$this->delete_value('query_lock_'.$LockName);
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|
2013-10-06 08:01:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get cache server status
|
|
|
|
*
|
|
|
|
* @return array (host => bool status, ...)
|
|
|
|
*/
|
|
|
|
public function server_status() {
|
|
|
|
$Status = array();
|
|
|
|
foreach ($this->Servers as $Server) {
|
|
|
|
$Status["$Server[host]:$Server[port]"] = $this->getServerStatus($Server['host'], $Server['port']);
|
|
|
|
}
|
|
|
|
return $Status;
|
|
|
|
}
|
2011-03-28 14:21:28 +00:00
|
|
|
}
|