From c5d23e27a6a34e02090ebcece60dfa5b7f814488 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 20:26:33 -1000 Subject: Make caching a plugin system --- lib/cache.php | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 lib/cache.php (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php new file mode 100644 index 000000000..d1ba65dab --- /dev/null +++ b/lib/cache.php @@ -0,0 +1,113 @@ +. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +/** + * Interface for caching + * + * An abstract interface for caching. + * + */ + +class Cache +{ + var $_items = array(); + static $_inst = null; + + static function instance() + { + if (is_null(self::$_inst)) { + self::$_inst = new Cache(); + } + + return self::$_inst; + } + + static function key($extra) + { + $base_key = common_config('memcached', 'base'); + + if (empty($base_key)) { + $base_key = common_keyize(common_config('site', 'name')); + } + + return 'statusnet:' . $base_key . ':' . $extra; + } + + static function keyize($str) + { + $str = strtolower($str); + $str = preg_replace('/\s/', '_', $str); + return $str; + } + + function get($key) + { + $value = null; + + if (!Event::handle('StartCacheGet', array(&$key, &$value))) { + if (array_key_exists($_items, $key)) { + common_log(LOG_INFO, 'Cache HIT for key ' . $key); + $value = $_items[$key]; + } else { + common_log(LOG_INFO, 'Cache MISS for key ' . $key); + } + Event::handle('EndCacheGet', array($key, &$value)); + } + + return $value; + } + + function set($key, $value, $flag=null, $expiry=null) + { + $success = false; + + if (!Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { + common_log(LOG_INFO, 'Setting cache value for key ' . $key); + $_items[$key] = $value; + $success = true; + Event::handle('EndCacheSet', array($key, $value, $flag, $expiry)); + } + + return $success; + } + + function delete($key) + { + $success = false; + + if (!Event::handle('StartCacheDelete', array(&$key, &$success))) { + common_log(LOG_INFO, 'Deleting cache value for key ' . $key); + unset($_items[$key]); + $success = true; + Event::handle('EndCacheDelete', array($key)); + } + + return $success; + } +} -- cgit v1.2.3-54-g00ecf From e1de1bf0fef4c7ae21ebca99dc4f38746c9d2df2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 20:32:56 -1000 Subject: fix default array implementation checks --- lib/cache.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index d1ba65dab..31d2f84d2 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -71,9 +71,9 @@ class Cache $value = null; if (!Event::handle('StartCacheGet', array(&$key, &$value))) { - if (array_key_exists($_items, $key)) { + if (array_key_exists($key, $this->_items)) { common_log(LOG_INFO, 'Cache HIT for key ' . $key); - $value = $_items[$key]; + $value = $this->_items[$key]; } else { common_log(LOG_INFO, 'Cache MISS for key ' . $key); } @@ -89,7 +89,7 @@ class Cache if (!Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { common_log(LOG_INFO, 'Setting cache value for key ' . $key); - $_items[$key] = $value; + $this->_items[$key] = $value; $success = true; Event::handle('EndCacheSet', array($key, $value, $flag, $expiry)); } @@ -102,8 +102,10 @@ class Cache $success = false; if (!Event::handle('StartCacheDelete', array(&$key, &$success))) { - common_log(LOG_INFO, 'Deleting cache value for key ' . $key); - unset($_items[$key]); + if (array_key_exists($key, $this->_items[$key])) { + common_log(LOG_INFO, 'Deleting cache value for key ' . $key); + unset($this->_items[$key]); + } $success = true; Event::handle('EndCacheDelete', array($key)); } -- cgit v1.2.3-54-g00ecf From 65d07b657dfbda0f696109769192d54856b5b8e8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 20:37:30 -1000 Subject: invert if for cache handling --- lib/cache.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index 31d2f84d2..9ea531c07 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -70,7 +70,7 @@ class Cache { $value = null; - if (!Event::handle('StartCacheGet', array(&$key, &$value))) { + if (Event::handle('StartCacheGet', array(&$key, &$value))) { if (array_key_exists($key, $this->_items)) { common_log(LOG_INFO, 'Cache HIT for key ' . $key); $value = $this->_items[$key]; @@ -87,7 +87,7 @@ class Cache { $success = false; - if (!Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { + if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { common_log(LOG_INFO, 'Setting cache value for key ' . $key); $this->_items[$key] = $value; $success = true; @@ -101,7 +101,7 @@ class Cache { $success = false; - if (!Event::handle('StartCacheDelete', array(&$key, &$success))) { + if (Event::handle('StartCacheDelete', array(&$key, &$success))) { if (array_key_exists($key, $this->_items[$key])) { common_log(LOG_INFO, 'Deleting cache value for key ' . $key); unset($this->_items[$key]); -- cgit v1.2.3-54-g00ecf From 1e1062ca9ca51ef618600459240bb0d497a47491 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 21:00:42 -1000 Subject: Make cache.php PHPCS-clean --- lib/cache.php | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index 9ea531c07..63f582861 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -30,15 +30,31 @@ /** * Interface for caching * - * An abstract interface for caching. + * An abstract interface for caching. Because we originally used the + * Memcache plugin directly, the interface uses a small subset of the + * Memcache interface. * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ */ class Cache { - var $_items = array(); + var $_items = array(); static $_inst = null; + /** + * Singleton constructor + * + * Use this to get the singleton instance of Cache. + * + * @return Cache cache object + */ + static function instance() { if (is_null(self::$_inst)) { @@ -48,6 +64,18 @@ class Cache return self::$_inst; } + /** + * Create a cache key from input text + * + * Builds a cache key from input text. Helps to namespace + * the cache area (if shared with other applications or sites) + * and prevent conflicts. + * + * @param string $extra the real part of the key + * + * @return string full key + */ + static function key($extra) { $base_key = common_config('memcached', 'base'); @@ -59,6 +87,16 @@ class Cache return 'statusnet:' . $base_key . ':' . $extra; } + /** + * Make a string suitable for use as a key + * + * Useful for turning primary keys of tables into cache keys. + * + * @param string $str string to turn into a key + * + * @return string keyized string + */ + static function keyize($str) { $str = strtolower($str); @@ -66,6 +104,16 @@ class Cache return $str; } + /** + * Get a value associated with a key + * + * The value should have been set previously. + * + * @param string $key Lookup key + * + * @return string retrieved value or null if unfound + */ + function get($key) { $value = null; @@ -83,20 +131,44 @@ class Cache return $value; } + /** + * Set the value associated with a key + * + * @param string $key The key to use for lookups + * @param string $value The value to store + * @param integer $flag Flags to use, mostly ignored + * @param integer $expiry Expiry value, mostly ignored + * + * @return boolean success flag + */ + function set($key, $value, $flag=null, $expiry=null) { $success = false; - if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { + if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag, + &$expiry, &$success))) { common_log(LOG_INFO, 'Setting cache value for key ' . $key); + $this->_items[$key] = $value; + $success = true; - Event::handle('EndCacheSet', array($key, $value, $flag, $expiry)); + + Event::handle('EndCacheSet', array($key, $value, $flag, + $expiry)); } return $success; } + /** + * Delete the value associated with a key + * + * @param string $key Key to delete + * + * @return boolean success flag + */ + function delete($key) { $success = false; -- cgit v1.2.3-54-g00ecf From cc5534d180625b3d34f7039c0b95b034f3674a20 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 21:16:59 -1000 Subject: First version of Memcache plugin --- lib/cache.php | 2 +- lib/common.php | 12 ++++ lib/default.php | 7 +- plugins/MemcachePlugin.php | 162 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 plugins/MemcachePlugin.php (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index 63f582861..23657bbf3 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -78,7 +78,7 @@ class Cache static function key($extra) { - $base_key = common_config('memcached', 'base'); + $base_key = common_config('cache', 'base'); if (empty($base_key)) { $base_key = common_keyize(common_config('site', 'name')); diff --git a/lib/common.php b/lib/common.php index 7fa1910af..b0e5c4390 100644 --- a/lib/common.php +++ b/lib/common.php @@ -210,6 +210,18 @@ if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db' $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini'; } +// Backwards compatibility + +if (array_key_exists('memcached', $config)) { + if ($config['memcached']['enabled']) { + addPlugin('Memcache', array('servers' => $config['memcached']['server'])); + } + + if (!empty($config['memcached']['base'])) { + $config['cache']['base'] = $config['memcached']['base']; + } +} + function __autoload($cls) { if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) { diff --git a/lib/default.php b/lib/default.php index 8a70ed3fa..eea11eb2b 100644 --- a/lib/default.php +++ b/lib/default.php @@ -147,11 +147,8 @@ $default = array('enabled' => true, 'consumer_key' => null, 'consumer_secret' => null), - 'memcached' => - array('enabled' => false, - 'server' => 'localhost', - 'base' => null, - 'port' => 11211), + 'cache' => + array('base' => null), 'ping' => array('notify' => array()), 'inboxes' => diff --git a/plugins/MemcachePlugin.php b/plugins/MemcachePlugin.php new file mode 100644 index 000000000..acbec135e --- /dev/null +++ b/plugins/MemcachePlugin.php @@ -0,0 +1,162 @@ +. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * A plugin to use memcache for the cache interface + * + * This used to be encoded as config-variable options in the core code; + * it's now broken out to a separate plugin. The same interface can be + * implemented by other plugins. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class MemcachePlugin extends Plugin +{ + private $_conn = null; + public $servers = array('127.0.0.1;11211'); + + /** + * Initialize the plugin + * + * Note that onStartCacheGet() may have been called before this! + * + * @return boolean flag value + */ + + function onInitializePlugin() + { + $this->_ensureConn(); + return true; + } + + /** + * Get a value associated with a key + * + * The value should have been set previously. + * + * @param string &$key in; Lookup key + * @param mixed &$value out; value associated with key + * + * @return boolean hook success + */ + + function onStartCacheGet(&$key, &$value) + { + $this->_ensureConn(); + $value = $this->_conn->get($key); + Event::handle('EndCacheGet', array($key, &$value)); + return false; + } + + /** + * Associate a value with a key + * + * @param string &$key in; Key to use for lookups + * @param mixed &$value in; Value to associate + * @param integer &$flag in; Flag (passed through to Memcache) + * @param integer &$expiry in; Expiry (passed through to Memcache) + * @param boolean &$success out; Whether the set was successful + * + * @return boolean hook success + */ + + function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success) + { + $this->_ensureConn(); + $success = $this->_conn->set($key, $value, $flag, $expiry); + Event::handle('EndCacheSet', array($key, $value, $flag, + $expiry)); + return false; + } + + /** + * Delete a value associated with a key + * + * @param string &$key in; Key to lookup + * @param boolean &$success out; whether it worked + * + * @return boolean hook success + */ + + function onStartCacheDelete(&$key, &$success) + { + $this->_ensureConn(); + $success = $this->_conn->delete($key); + Event::handle('EndCacheDelete', array($key)); + return false; + } + + /** + * Ensure that a connection exists + * + * Checks the instance $_conn variable and connects + * if it is empty. + * + * @return void + */ + + private function _ensureConn() + { + if (empty($this->_conn)) { + $this->_conn = new Memcache(); + + if (is_array($this->servers)) { + foreach ($this->servers as $server) { + list($host, $port) = explode(';', $server); + if (empty($port)) { + $port = 11211; + } + + $this->_conn->addServer($host, $port); + } + } else { + $this->_conn->addServer($this->servers); + list($host, $port) = explode(';', $this->servers); + if (empty($port)) { + $port = 11211; + } + $this->_conn->addServer($host, $port); + } + } + } +} + -- cgit v1.2.3-54-g00ecf From bfa3aa0e7f39ba9b8ff920c480efb4cf52007532 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 3 Jan 2010 11:28:15 -1000 Subject: Remove logging from default cache --- lib/cache.php | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index 23657bbf3..253839fb1 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -120,10 +120,7 @@ class Cache if (Event::handle('StartCacheGet', array(&$key, &$value))) { if (array_key_exists($key, $this->_items)) { - common_log(LOG_INFO, 'Cache HIT for key ' . $key); $value = $this->_items[$key]; - } else { - common_log(LOG_INFO, 'Cache MISS for key ' . $key); } Event::handle('EndCacheGet', array($key, &$value)); } @@ -148,7 +145,6 @@ class Cache if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { - common_log(LOG_INFO, 'Setting cache value for key ' . $key); $this->_items[$key] = $value; @@ -175,7 +171,6 @@ class Cache if (Event::handle('StartCacheDelete', array(&$key, &$success))) { if (array_key_exists($key, $this->_items[$key])) { - common_log(LOG_INFO, 'Deleting cache value for key ' . $key); unset($this->_items[$key]); } $success = true; -- cgit v1.2.3-54-g00ecf From a1821ec8afd40c3ab88546a8fa2e6fc03a19ad25 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 4 Jan 2010 09:59:47 -1000 Subject: default value for cache::get() changed from null to false --- lib/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index 253839fb1..bac3499e5 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -116,7 +116,7 @@ class Cache function get($key) { - $value = null; + $value = false; if (Event::handle('StartCacheGet', array(&$key, &$value))) { if (array_key_exists($key, $this->_items)) { -- cgit v1.2.3-54-g00ecf From 16254c14c8984f457b13c32d2b9bc0baedfde448 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 5 Jan 2010 09:54:43 -0800 Subject: Typo fix in the new default in-process cache; spewed notice warnings on deletion, breaking XHR responses. --- lib/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/cache.php') diff --git a/lib/cache.php b/lib/cache.php index bac3499e5..85e8badc1 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -170,7 +170,7 @@ class Cache $success = false; if (Event::handle('StartCacheDelete', array(&$key, &$success))) { - if (array_key_exists($key, $this->_items[$key])) { + if (array_key_exists($key, $this->_items)) { unset($this->_items[$key]); } $success = true; -- cgit v1.2.3-54-g00ecf From aff78e51216e09a6e5c95c775d636530c85736fc Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 5 Jan 2010 15:05:53 -0800 Subject: Cache fixes: * We now cache negative lookups; clear them in Memcached_DataObject->insert() * Mark file.url as a unique key in statusnet.ini so its negative lookups are cleared properly (first save of a notice with a new URL was failing due to double-insert) * Now using serialization for default in-process cache instead of just saving objects; avoids potential corruption if you save an object to cache, change the original object, then fetch the same key from cache again --- classes/Memcached_DataObject.php | 1 + classes/statusnet.ini | 1 + lib/cache.php | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/cache.php') diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index aab1cace6..c31b2a546 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -142,6 +142,7 @@ class Memcached_DataObject extends DB_DataObject function insert() { + $this->decache(); // in case of cached negative lookups $result = parent::insert(); return $result; } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index ac31148da..0db2c5d6e 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -92,6 +92,7 @@ modified = 384 [file__keys] id = N +url = U [file_oembed] file_id = 129 diff --git a/lib/cache.php b/lib/cache.php index 85e8badc1..b7b34c050 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -120,7 +120,7 @@ class Cache if (Event::handle('StartCacheGet', array(&$key, &$value))) { if (array_key_exists($key, $this->_items)) { - $value = $this->_items[$key]; + $value = unserialize($this->_items[$key]); } Event::handle('EndCacheGet', array($key, &$value)); } @@ -146,7 +146,7 @@ class Cache if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag, &$expiry, &$success))) { - $this->_items[$key] = $value; + $this->_items[$key] = serialize($value); $success = true; -- cgit v1.2.3-54-g00ecf