From 28731fbbb91748b109424985e4083fd79775a63c Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 7 Jul 2016 17:39:07 +0200 Subject: [PATCH] * APCu/PHP7: fixed not used APCu under PHP7, as it has no APC compatible interface Apcu class is basicly a copy of Apc, but as its methods are called quite a lot, I dont want to add the overhad of checking to call apc_ or apcu_ on every call, anyway APC died with PHP 5.5 so we can remove Apcu class once we no longer support PHP 5.4 --- api/src/Cache.php | 10 ++- api/src/Cache/Apcu.php | 184 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 api/src/Cache/Apcu.php diff --git a/api/src/Cache.php b/api/src/Cache.php index fa8b8f9e90..01ac99188d 100644 --- a/api/src/Cache.php +++ b/api/src/Cache.php @@ -555,7 +555,7 @@ class Cache } if (!$providers[$level] && $log_not_found) error_log(__METHOD__."($level) no provider found ($reason)!".function_backtrace()); } - //error_log(__METHOD__."($level) = ".array2string($providers[$level]).', cache_provider='.array2string($GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)])); + error_log(__METHOD__."($level) = ".array2string($providers[$level]).', cache_provider='.array2string($GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)])); return $providers[$level]; } @@ -730,11 +730,13 @@ class Cache } } -// setting apc as default provider, if apc_fetch function exists AND further checks in Api\Cache\Apc recommed it +// setting apc(u) as default provider, if apc(u)_fetch function exists AND further checks in Api\Cache\Apc(u) recommed it if (is_null(Cache::$default_provider)) { - Cache::$default_provider = function_exists('apc_fetch') && Cache\Apc::available() ? - 'EGroupware\Api\Cache\Apc' : 'EGroupware\Api\Cache\Files'; + Cache::$default_provider = + function_exists('apcu_fetch') && Cache\Apcu::available() ? 'EGroupware\Api\Cache\Apcu' : + (function_exists('apc_fetch') && Cache\Apc::available() ? 'EGroupware\Api\Cache\Apc' : + 'EGroupware\Api\Cache\Files'); } //error_log('Cache::$default_provider='.array2string(Cache::$default_provider)); diff --git a/api/src/Cache/Apcu.php b/api/src/Cache/Apcu.php new file mode 100644 index 0000000000..610d091f5f --- /dev/null +++ b/api/src/Cache/Apcu.php @@ -0,0 +1,184 @@ + + * @copyright (c) 2010-16 by Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Cache; + +/** + * Caching provider storing data in PHP's APCu extension / shared memory. + * + * The provider concats all $keys with '::' to get a single string. + * + * This provider is used by default, if it is available or explicit enabled in your header.inc.php: + * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('EGroupware\Api\Cache\Apc'); + * and optional also $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance) + * + * APC(u) and CLI: + * -------------- + * APC(u) is not enabled by default for CLI (apc.enable_cli), nor would it access same shared memory! + * It makes no sense to fall back to files cache, as this is probably quite outdated, + * if PHP via Webserver uses APC. Better to use no cache at all. + * Api\Cache::get*() will return NULL for not found and Api\Cache::[un]set*() + * false for not being able to (un)set anything. + * It also does not make sense to report failure by throwing an Exception and filling + * up cron logs. + * --> if APC(u) is available for Webserver, we report availability for CLI too, + * but use no cache at all! + */ +class Apcu extends Base implements Provider +{ + /** + * Constructor, eg. opens the connection to the backend + * + * @throws Exception if connection to backend could not be established + * @param array $params eg. array('localhost'[,'localhost:11211',...]) + */ + function __construct(array $params) + { + if (!function_exists('apcu_fetch')) // apc >= 3.0 + { + throw new Exception (__METHOD__.'('.array2string($params).") No function apcu_fetch()!"); + } + } + + /** + * Check if APC is available for caching user data + * + * Default shared memory size of 32M is just enough for the byte code cache, + * but not for caching user data, we only use APC by default if we have at least 64M. + * + * @return boolean true: apc available, false: not + */ + public static function available() + { + if (($available = (bool)ini_get('apc.enabled') && function_exists('apcu_fetch'))) + { + $size = ini_get('apc.shm_size'); + + switch(strtoupper(substr($size, -1))) + { + case 'G': + $size *= 1024; + case 'M': + $size *= 1024; + case 'K': + $size *= 1024; + } + $size *= ini_get('apc.shm_segments'); + + // only cache in APC, if we have at least 64M available (default is 32M) + $available = $size >= 67108864; + } + //error_log(__METHOD__."() size=$size returning ".array2string($available)); + return $available; + } + + /** + * Stores some data in the cache, if it does NOT already exists there + * + * @param array $keys eg. array($level,$app,$location) + * @param mixed $data + * @param int $expiration =0 + * @return boolean true on success, false on error, incl. key already exists in cache + */ + function add(array $keys,$data,$expiration=0) + { + return apcu_add(self::key($keys),$data,$expiration); + } + + /** + * Stores some data in the cache + * + * @param array $keys eg. array($level,$app,$location) + * @param mixed $data + * @param int $expiration =0 + * @return boolean true on success, false on error + */ + function set(array $keys,$data,$expiration=0) + { + return apcu_store(self::key($keys),$data,$expiration); + } + + /** + * Get some data from the cache + * + * @param array $keys eg. array($level,$app,$location) + * @return mixed data stored or NULL if not found in cache + */ + function get(array $keys) + { + $success = null; + $data = apcu_fetch($key=self::key($keys),$success); + + if (!$success) + { + //error_log(__METHOD__."(".array2string($keys).") key='$key' NOT found!"); + return null; + } + //error_log(__METHOD__."(".array2string($keys).") key='$key' found ".bytes(serialize($data))." bytes)."); + return $data; + } + + /** + * Delete some data from the cache + * + * @param array $keys eg. array($level,$app,$location) + * @return boolean true on success, false on error (eg. $key not set) + */ + function delete(array $keys) + { + return apcu_delete(self::key($keys)); + } + + /** + * Delete all data under given keys + * + * @param array $keys eg. array($level,$app,$location) + * @return boolean true on success, false on error (eg. $key not set) + */ + function flush(array $keys) + { + // APCu > 5 has APCUIterator + if (class_exists('APCUIterator')) + { + $iterator = new \APCUIterator($preg='/^'.preg_quote(self::key($keys).'/')); + } + // APC >= 3.1.1, but also seems to be missing if apc is disabled eg. for cli + elseif(class_exists('APCIterator')) + { + $iterator = new \APCIterator('user', $preg='/^'.preg_quote(self::key($keys).'/')); + } + else + { + if (function_exists('apcu_clear_cache')) apcu_clear_cache (); + + return false; + } + foreach($iterator as $item) + { + //error_log(__METHOD__."(".array2string($keys).") preg='$preg': calling apcu_delete('$item[key]')"); + apcu_delete($item['key']); + } + return true; + } + + /** + * Create a single key from $keys + * + * @param array $keys + * @return string + */ + private static function key(array $keys) + { + return implode('::',$keys); + } +}