Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F726442
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, Jan 5, 5:14 PM
Size
23 KB
Mime Type
text/x-diff
Expires
Tue, Jan 7, 5:14 PM (20 h, 30 m ago)
Engine
blob
Format
Raw Data
Handle
536376
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.2.x/core/kernel/utility/cache.php
===================================================================
--- branches/5.2.x/core/kernel/utility/cache.php (revision 15309)
+++ branches/5.2.x/core/kernel/utility/cache.php (revision 15310)
@@ -1,979 +1,979 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Manager of all implemented caching handlers
*
*/
class kCache extends kBase {
/**
* Rebuild cache now
*
*/
const REBUILD_NOW = 1;
/**
* Rebuild cache later
*
*/
const REBUILD_LATER = 2;
/**
* Cache waiting step (in seconds)
*
*/
const WAIT_STEP = 2;
/**
* Object of cache handler
*
* @var FakeCacheHandler
*/
var $_handler = null;
/**
* Part of what we retrieve will be stored locally (per script run) not to bother memcache a lot
*
* @var Array
*/
var $_localStorage = Array ();
/**
* What type of caching is being used
*
* @var int
*/
var $cachingType = CACHING_TYPE_NONE;
/**
* Debug cache usage
*
* @var bool
*/
var $debugCache = false;
/**
* Displays cache usage statistics
*
* @var bool
* @access protected
*/
protected $_storeStatistics = false;
/**
* Site key name
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyName = '';
/**
* Site key value
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyValue = null;
/**
* Creates cache manager
*
* @access public
*/
public function __construct()
{
parent::__construct();
$vars = kUtil::getConfigVars();
$this->siteKeyName = 'site_serial:' . crc32(SQL_TYPE . '://' . SQL_USER . ':' . SQL_PASS . '@' . SQL_SERVER . ':' . TABLE_PREFIX . ':' . SQL_DB);
// get cache handler class to use
$handler_class = (isset($vars['CacheHandler']) ? $vars['CacheHandler'] : '') . 'CacheHandler';
// defined cache handler doesn't exist -> use default
if ( !class_exists($handler_class) ) {
$handler_class = 'FakeCacheHandler';
}
$handler = new $handler_class($this);
if ( !$handler->isWorking() ) {
// defined cache handler is not working -> use default
trigger_error('Failed to initialize "<strong>' . $handler_class . '</strong>" caching handler.', E_USER_WARNING);
$handler = new FakeCacheHandler($this);
}
elseif ( $this->Application->isDebugMode() && ($handler->getCachingType() == CACHING_TYPE_MEMORY) ) {
$this->Application->Debugger->appendHTML('Memory Caching: "<strong>' . $handler_class . '</strong>"');
}
$this->_handler = $handler;
$this->cachingType = $handler->getCachingType();
$this->debugCache = $handler->getCachingType() == CACHING_TYPE_MEMORY && $this->Application->isDebugMode();
$this->_storeStatistics = defined('DBG_CACHE') && DBG_CACHE;
if ( $this->_storeStatistics ) {
// don't use FileHelper, since kFactory isn't ready yet
if ( !file_exists(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage') ) {
mkdir(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage');
}
}
}
/**
* Returns caching type of current storage engine
*
* @return int
*/
function getCachingType()
{
return $this->cachingType;
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function setCache($name, $value, $expiration)
{
// 1. stores current version of serial for given cache key
$this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration);
$this->storeStatistics($name, 'W');
// 2. don't replace serials within the key
$saved = $this->_setCache($name, $value, $expiration);
$this->storeStatistics($name, 'U');
// 3. remove rebuilding mark
$this->delete($name . '_rebuilding');
return $saved;
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function _setCache($name, $value, $expiration)
{
$prepared_name = $this->prepareKeyName($name);
$this->_localStorage[$prepared_name] = $value;
return $this->_handler->set($prepared_name, $value, $expiration);
}
/**
* Stores value to cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function addCache($name, $value, $expiration)
{
// 1. stores current version of serial for given cache key
$this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration);
// 2. remove rebuilding mark
$this->delete($name . '_rebuilding');
// 3. don't replace serials within the key
return $this->_addCache($name, $value, $expiration);
}
/**
* Stores value to cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function _addCache($name, $value, $expiration)
{
$prepared_name = $this->prepareKeyName($name);
$added = $this->_handler->add($prepared_name, $value, $expiration);
if ( $added ) {
$this->_localStorage[$prepared_name] = $value;
}
return $added;
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @param string $miss_type
* @return bool
*/
function rebuildCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M')
{
if ( !isset($mode) || $mode == self::REBUILD_NOW ) {
$this->storeStatistics($name, $miss_type);
if ( !$max_rebuilding_time ) {
return true;
}
// prevent parallel rebuild attempt by using "add" instead of "set" method
if ( !$this->_addCache($name . '_rebuilding', 1, $max_rebuilding_time) ) {
$this->storeStatistics($name, 'l');
return false;
}
$this->storeStatistics($name, 'L');
$this->delete($name . '_rebuild');
}
elseif ( $mode == self::REBUILD_LATER ) {
$this->_setCache($name . '_rebuild', 1, 0);
$this->delete($name . '_rebuilding');
}
return true;
}
/**
* Returns value from cache
*
* @param string $name
* @param bool $store_locally store data locally after retrieved
* @param int $max_rebuild_seconds
* @return mixed
*/
function getCache($name, $store_locally = true, $max_rebuild_seconds = 0)
{
- $cached_data = $this->_getCache(Array ($name . '_rebuild', $name . '_serials'), Array (true, false));
+ $cached_data = $this->_getCache(Array ($name . '_rebuild', $name . '_serials'), Array (true, true));
if ( $cached_data[$name . '_rebuild'] ) {
// cache rebuild requested -> rebuild now
$this->delete($name . '_rebuild');
if ( $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) {
return false;
}
}
// There are 2 key types:
// - with serials, e.g. with_serial_key[%LangSerial%]
// - without serials, e.g. without_serial
// Evaluated serials of each cache key are stored in '{$name}_serials' cache key.
// If cache is present, but serial is outdated, then cache value is assumed to be outdated.
$new_serial = $this->replaceSerials($name);
$old_serial = $cached_data[$name . '_serials'];
if ( $name == $new_serial || $new_serial != $old_serial ) {
// no serials in cache key OR cache is outdated
$wait_seconds = $max_rebuild_seconds;
while (true) {
$cached_data = $this->_getCache(Array ($name, $name . '_rebuilding'), Array ($store_locally, false));
$cache = $cached_data[$name];
$rebuilding = $cached_data[$name . '_rebuilding'];
if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) {
// cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
return false;
}
elseif ( $cache !== false ) {
// re-read serial, since it might have been changed in parallel process !!!
$old_serial = $this->_getCache($name . '_serials', false);
// cache present (if other user is rebuilding it, then it's outdated cache) -> return it
if ( $rebuilding || $new_serial == $old_serial ) {
$this->storeStatistics($name, $rebuilding ? 'h' : 'H');
return $cache;
}
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
return false;
}
$wait_seconds -= self::WAIT_STEP;
sleep(self::WAIT_STEP);
}
}
$cache = $this->_getCache($name, $store_locally);
if ( $cache === false ) {
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M4]');
}
else {
$this->storeStatistics($name, 'H');
}
return $cache;
}
/**
* Returns cached value from local cache
*
* @param string $prepared_name Prepared key name from kCache::prepareKeyName() function
* @return mixed
* @see prepareKeyName
* @access public
*/
public function getFromLocalStorage($prepared_name)
{
return array_key_exists($prepared_name, $this->_localStorage) ? $this->_localStorage[$prepared_name] : false;
}
/**
* Returns value from cache
*
* @param string|Array $names
* @param bool|Array $store_locally store data locally after retrieved
* @return mixed
*/
function _getCache($names, $store_locally = true)
{
static $request_number = 1;
$res = Array ();
$names = (array)$names;
$store_locally = (array)$store_locally;
$to_get = $prepared_names = array_map(Array (&$this, 'prepareKeyName'), $names);
foreach ($prepared_names as $index => $prepared_name) {
$name = $names[$index];
if ( $store_locally[$index] && array_key_exists($prepared_name, $this->_localStorage) ) {
$res[$name] = $this->_localStorage[$prepared_name];
unset($to_get[$index]);
}
}
if ( $to_get ) {
$multi_res = $this->_handler->get($to_get);
foreach ($to_get as $index => $prepared_name) {
$name = $names[$index];
if ( array_key_exists($prepared_name, $multi_res) ) {
$res[$name] =& $multi_res[$prepared_name];
}
else {
$res[$name] = false;
}
$this->_postProcessGetCache($prepared_name, $res[$name], $store_locally[$index], $request_number);
}
$request_number++;
}
return count($res) == 1 ? array_pop($res) : $res;
}
/**
* Stores variable in local cache & collects debug info about cache
*
* @param string $name
* @param mixed $res
* @param bool $store_locally
* @param int $request_number
* @return void
* @access protected
*/
protected function _postProcessGetCache($name, &$res, $store_locally = true, $request_number)
{
if ( $this->debugCache ) {
// don't display subsequent serial cache retrievals (ones, that are part of keys)
if ( is_array($res) ) {
$this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '". Type: ' . gettype($res) . '.');
}
else {
$res_display = strip_tags($res);
if ( strlen($res_display) > 200 ) {
$res_display = substr($res_display, 0, 50) . ' ...';
}
$this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '" resulted [' . $res_display . ']');
}
}
if ( $store_locally /*&& ($res !== false)*/ ) {
$this->_localStorage[$name] = $res;
}
}
/**
* Deletes value from cache
*
* @param string $name
* @return mixed
*/
function delete($name)
{
$name = $this->prepareKeyName($name);
unset($this->_localStorage[$name]);
return $this->_handler->delete($name);
}
/**
* Reset's all memory cache at once
*/
function reset()
{
// don't check for enabled, because we maybe need to reset cache anyway
if ($this->cachingType == CACHING_TYPE_TEMPORARY) {
return ;
}
$site_key = $this->_cachePrefix(true);
$this->_handler->set($site_key, $this->_handler->get($site_key) + 1);
}
/**
* Replaces serials and adds unique site prefix to cache variable name
*
* @param string $name
* @return string
*/
protected function prepareKeyName($name)
{
if ( $this->cachingType == CACHING_TYPE_TEMPORARY ) {
return $name;
}
// add site-wide prefix to key
return $this->_cachePrefix() . $name;
}
/**
* Replaces serials within given string
*
* @param string $value
* @return string
* @access protected
*/
protected function replaceSerials($value)
{
if ( preg_match_all('/\[%(.*?)%\]/', $value, $regs) ) {
// [%LangSerial%] - prefix-wide serial in case of any change in "lang" prefix
// [%LangIDSerial:5%] - one id-wide serial in case of data, associated with given id was changed
// [%CiIDSerial:ItemResourceId:5%] - foreign key-based serial in case of data, associated with given foreign key was changed
$serial_names = $regs[1];
$serial_count = count($serial_names);
$store_locally = Array ();
for ($i = 0; $i < $serial_count; $i++) {
$store_locally[] = true;
}
$serial_values = $this->_getCache($serial_names, $store_locally);
if ( !is_array($serial_values) ) {
$serial_values = Array (current($serial_names) => $serial_values);
}
foreach ($serial_names as $serial_name) {
$value = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $serial_values[$serial_name] . ']', $value);
}
}
return $value;
}
/**
* Returns site-wide caching prefix
*
* @param bool $only_site_key_name
* @return string
*/
function _cachePrefix($only_site_key_name = false)
{
if ($only_site_key_name) {
return $this->siteKeyName;
}
if ( !isset($this->siteKeyValue) ) {
$this->siteKeyValue = $this->_handler->get($this->siteKeyName);
if (!$this->siteKeyValue) {
$this->siteKeyValue = 1;
$this->_handler->set($this->siteKeyName, $this->siteKeyValue);
}
}
return "{$this->siteKeyName}:{$this->siteKeyValue}:";
}
/**
* Stores statistics about cache usage in a file (one file per cache)
*
* @param string $name
* @param string $action_type {M - miss, L - lock, W - write, U - unlock, H - actual hit, h - outdated hit}
* @return void
* @access public
*/
public function storeStatistics($name, $action_type)
{
if ( !$this->_storeStatistics ) {
return;
}
$name = str_replace(Array ('/', '\\', ':'), '_', $name);
$fp = fopen(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage' . DIRECTORY_SEPARATOR . $name, 'a');
fwrite($fp, $action_type);
fclose($fp);
}
}
abstract class kCacheHandler {
/**
* Remembers status of cache handler (working or not)
*
* @var bool
* @access protected
*/
protected $_enabled = false;
/**
* Caching type that caching handler implements
*
* @var int
* @access protected
*/
protected $cachingType;
/**
*
* @var kCache
* @access protected
*/
protected $parent;
public function __construct(kCache $parent)
{
$this->parent = $parent;
}
/**
* Retrieves value from cache
*
* @param string $names
* @return mixed
* @access public
*/
abstract public function get($names);
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
abstract public function set($name, $value, $expiration = 0);
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
abstract public function add($name, $value, $expiration = 0);
/**
* Deletes key from cach
*
* @param string $name
* @return bool
* @access public
*/
abstract public function delete($name);
/**
* Determines, that cache storage is working fine
*
* @return bool
* @access public
*/
public function isWorking()
{
return $this->_enabled;
}
/**
* Returns caching type of current storage engine
*
* @return int
* @access public
*/
public function getCachingType()
{
return $this->cachingType;
}
}
class FakeCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->_enabled = true;
$this->cachingType = CACHING_TYPE_TEMPORARY;
}
/**
* Retrieves value from cache
*
* @param string|Array $names
* @return mixed
* @access public
*/
public function get($names)
{
if ( is_array($names) ) {
$res = Array ();
foreach ($names as $name) {
$res[$name] = $this->parent->getFromLocalStorage($name);
}
return $res;
}
return $this->parent->getFromLocalStorage($names);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return true;
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
return true;
}
/**
* Deletes key from cach
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return true;
}
}
class MemcacheCacheHandler extends kCacheHandler {
/**
* Memcache connection
*
* @var Memcache
* @access protected
*/
protected $_handler = null;
public function __construct(kCache $parent, $default_servers = '')
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$vars = kUtil::getConfigVars();
$memcached_servers = isset($vars['MemcacheServers']) ? $vars['MemcacheServers'] : $default_servers;
if ( $memcached_servers && class_exists('Memcache') ) {
$this->_enabled = true;
$this->_handler = new Memcache();
$servers = explode(';', $memcached_servers);
foreach ($servers as $server) {
if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) {
// "hostname:port" OR "unix:///path/to/socket:0"
$server = $regs[1];
$port = $regs[2];
}
else {
$port = 11211;
}
$this->_handler->addServer($server, $port);
}
// verify, that memcache server is working
if ( !$this->_handler->set('test', 1) ) {
$this->_enabled = false;
}
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
* @access public
*/
public function get($name)
{
return $this->_handler->get($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
// 0 - don't use compression
return $this->_handler->set($name, $value, 0, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
// 0 - don't use compression
return $this->_handler->add($name, $value, 0, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return $this->_handler->delete($name, 0);
}
}
class ApcCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$this->_enabled = function_exists('apc_fetch');
// verify, that apc is working
if ( $this->_enabled && !$this->set('test', 1) ) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
* @access public
*/
public function get($name)
{
return apc_fetch($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return apc_store($name, $value, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
return apc_add($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return apc_delete($name);
}
}
class XCacheCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$this->_enabled = function_exists('xcache_get');
// verify, that xcache is working
if ( $this->_enabled && !$this->set('test', 1) ) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string|Array $names
* @return mixed
* @access public
*/
public function get($names)
{
if ( is_array($names) ) {
$res = Array ();
foreach ($names as $name) {
$res[$name] = $this->get($name);
}
return $res;
}
return xcache_isset($names) ? xcache_get($names) : false;
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return xcache_set($name, $value, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
// not atomic operation, like in Memcached and may fail
if ( xcache_isset($name) ) {
return false;
}
return $this->set($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return xcache_unset($name);
}
}
\ No newline at end of file
Event Timeline
Log In to Comment