diff options
-rw-r--r-- | classes/Memcached_DataObject.php | 23 | ||||
-rw-r--r-- | classes/Notice.php | 2 | ||||
-rw-r--r-- | lib/cache.php | 187 | ||||
-rw-r--r-- | lib/common.php | 12 | ||||
-rw-r--r-- | lib/default.php | 7 | ||||
-rw-r--r-- | lib/noticelist.php | 8 | ||||
-rw-r--r-- | lib/util.php | 33 | ||||
-rw-r--r-- | plugins/APCPlugin.php | 108 | ||||
-rw-r--r-- | plugins/MemcachePlugin.php | 162 |
9 files changed, 495 insertions, 47 deletions
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index be8137573..1608720d1 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -23,7 +23,7 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Memcached_DataObject extends DB_DataObject { - /** + /** * Destructor to free global memory resources associated with * this data object when it's unset or goes out of scope. * DB_DataObject doesn't do this yet by itself. @@ -60,18 +60,17 @@ class Memcached_DataObject extends DB_DataObject if ($i) { return $i; } else { - $i = DB_DataObject::staticGet($cls, $k, $v); - if ($i) { - // DB_DataObject's in-process lookup cache interferes with GC - // to cause massive memory leaks in long-running processes. - if (php_sapi_name() == 'cli') { - $i->_clear_cache(); - } - - // Now store it into the shared memcached, if present... + $i = DB_DataObject::factory($cls); + if (empty($i)) { + return false; + } + $result = $i->get($k, $v); + if ($result) { $i->encache(); + return $i; + } else { + return false; } - return $i; } } @@ -123,7 +122,7 @@ class Memcached_DataObject extends DB_DataObject } static function cacheKey($cls, $k, $v) { - if (is_object($cls) || is_object($j) || is_object($v)) { + if (is_object($cls) || is_object($k) || is_object($v)) { $e = new Exception(); common_log(LOG_ERR, __METHOD__ . ' object in param: ' . str_replace("\n", " ", $e->getTraceAsString())); diff --git a/classes/Notice.php b/classes/Notice.php index fe3f3c017..3e55bd6fa 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -214,7 +214,7 @@ class Notice extends Memcached_DataObject extract($options); } - if (empty($is_local)) { + if (!isset($is_local)) { $is_local = Notice::LOCAL_PUBLIC; } diff --git a/lib/cache.php b/lib/cache.php new file mode 100644 index 000000000..23657bbf3 --- /dev/null +++ b/lib/cache.php @@ -0,0 +1,187 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Cache interface plus default in-memory cache implementation + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @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. 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 <evan@status.net> + * @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(); + 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)) { + self::$_inst = new 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('cache', 'base'); + + if (empty($base_key)) { + $base_key = common_keyize(common_config('site', 'name')); + } + + 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); + $str = preg_replace('/\s/', '_', $str); + 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; + + 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)); + } + + 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))) { + common_log(LOG_INFO, 'Setting cache value for key ' . $key); + + $this->_items[$key] = $value; + + $success = true; + + 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; + + 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; + Event::handle('EndCacheDelete', array($key)); + } + + return $success; + } +} 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/lib/noticelist.php b/lib/noticelist.php index 4c11ceed6..5eb2633ac 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -191,6 +191,14 @@ class NoticeListItem extends Widget function show() { + if (empty($this->notice)) { + common_log(LOG_WARNING, "Trying to show missing notice; skipping."); + return; + } else if (empty($this->profile)) { + common_log(LOG_WARNING, "Trying to show missing profile (" . $this->notice->profile_id . "); skipping."); + return; + } + $this->showStart(); if (Event::handle('StartShowNoticeItem', array($this))) { $this->showNotice(); diff --git a/lib/util.php b/lib/util.php index df3110ddd..63656b604 100644 --- a/lib/util.php +++ b/lib/util.php @@ -62,7 +62,7 @@ function common_init_language() // gettext will still select the right language. $language = common_language(); $locale_set = common_init_locale($language); - + setlocale(LC_CTYPE, 'C'); // So we do not have to make people install the gettext locales $path = common_config('site','locale_path'); @@ -1384,42 +1384,17 @@ function common_session_token() function common_cache_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; + return Cache::key($extra); } function common_keyize($str) { - $str = strtolower($str); - $str = preg_replace('/\s/', '_', $str); - return $str; + return Cache::keyize($str); } function common_memcache() { - static $cache = null; - if (!common_config('memcached', 'enabled')) { - return null; - } else { - if (!$cache) { - $cache = new Memcache(); - $servers = common_config('memcached', 'server'); - if (is_array($servers)) { - foreach($servers as $server) { - $cache->addServer($server); - } - } else { - $cache->addServer($servers); - } - $cache->setCompressThreshold(20000, 0.2); - } - return $cache; - } + return Cache::instance(); } function common_license_terms($uri) diff --git a/plugins/APCPlugin.php b/plugins/APCPlugin.php new file mode 100644 index 000000000..18409e29e --- /dev/null +++ b/plugins/APCPlugin.php @@ -0,0 +1,108 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, Inc. + * + * Plugin to implement cache interface for APC variable cache + * + * PHP version 5 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @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 APC's variable cache for the cache interface + * + * New plugin interface lets us use alternative cache systems + * for caching. This one uses APC's variable cache. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @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 APCPlugin extends Plugin +{ + /** + * 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) + { + $value = apc_fetch($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) + { + $success = apc_store($key, $value, ((is_null($expiry)) ? 0 : $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) + { + $success = apc_delete($key); + Event::handle('EndCacheDelete', array($key)); + return false; + } +} + 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 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, Inc. + * + * Plugin to implement cache interface for memcache + * + * PHP version 5 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @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 <evan@status.net> + * @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); + } + } + } +} + |