if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-
class Memcached_DataObject extends DB_DataObject
{
/**
}
}
+ /**
+ * Magic function called at serialize() time.
+ *
+ * We use this to drop a couple process-specific references
+ * from DB_DataObject which can cause trouble in future
+ * processes.
+ *
+ * @return array of variable names to include in serialization.
+ */
+ function __sleep()
+ {
+ $vars = array_keys(get_object_vars($this));
+ $skip = array('_DB_resultid', '_link_loaded');
+ return array_diff($vars, $skip);
+ }
+
+ /**
+ * Magic function called at unserialize() time.
+ *
+ * Clean out some process-specific variables which might
+ * be floating around from a previous process's cached
+ * objects.
+ *
+ * Old cached objects may still have them.
+ */
+ function __wakeup()
+ {
+ // Refers to global state info from a previous process.
+ // Clear this out so we don't accidentally break global
+ // state in *this* process.
+ $this->_DB_resultid = null;
+
+ // We don't have any local DBO refs, so clear these out.
+ $this->_link_loaded = false;
+ }
+
/**
* Wrapper for DB_DataObject's static lookup using memcached
* as backing instead of an in-process cache array.
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)) {
+ $i = false;
+ return $i;
+ }
+ $result = $i->get($k, $v);
+ if ($result) {
$i->encache();
+ return $i;
+ } else {
+ $i = false;
+ return $i;
}
- return $i;
}
}
}
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()));
if (!$c) {
return false;
} else {
- return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
+ $obj = $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
+ if (0 == strcasecmp($cls, 'User')) {
+ // Special case for User
+ if (is_object($obj) && is_object($obj->id)) {
+ common_log(LOG_ERR, "User " . $obj->nickname . " was cached with User as ID; deleting");
+ $c->delete(Memcached_DataObject::cacheKey($cls, $k, $v));
+ return false;
+ }
+ }
+ return $obj;
}
}
$c = $this->memcache();
if (!$c) {
return false;
+ } else if ($this->tableName() == 'user' && is_object($this->id)) {
+ // Special case for User bug
+ $e = new Exception();
+ common_log(LOG_ERR, __METHOD__ . ' caching user with User object as ID ' .
+ str_replace("\n", " ", $e->getTraceAsString()));
+ return false;
} else {
$pkey = array();
$pval = array();
return new ArrayWrapper($cached);
}
+ /**
+ * sends query to database - this is the private one that must work
+ * - internal functions use this rather than $this->query()
+ *
+ * Overridden to do logging.
+ *
+ * @param string $string
+ * @access private
+ * @return mixed none or PEAR_Error
+ */
+ function _query($string)
+ {
+ $start = microtime(true);
+ $result = parent::_query($string);
+ $delta = microtime(true) - $start;
+
+ $limit = common_config('db', 'log_slow_queries');
+ if (($limit > 0 && $delta >= $limit) || common_config('db', 'log_queries')) {
+ $clean = $this->sanitizeQuery($string);
+ common_log(LOG_DEBUG, sprintf("DB query (%0.3fs): %s", $delta, $clean));
+ }
+ return $result;
+ }
+
+ // Sanitize a query for logging
+ // @fixme don't trim spaces in string literals
+ function sanitizeQuery($string)
+ {
+ $string = preg_replace('/\s+/', ' ', $string);
+ $string = trim($string);
+ return $string;
+ }
+
// We overload so that 'SET NAMES "utf8"' is called for
// each connection
$exists = false;
}
+ // @fixme horrible evil hack!
+ //
+ // In multisite configuration we don't want to keep around a separate
+ // connection for every database; we could end up with thousands of
+ // connections open per thread. In an ideal world we might keep
+ // a connection per server and select different databases, but that'd
+ // be reliant on having the same db username/pass as well.
+ //
+ // MySQL connections are cheap enough we're going to try just
+ // closing out the old connection and reopening when we encounter
+ // a new DSN.
+ //
+ // WARNING WARNING if we end up actually using multiple DBs at a time
+ // we'll need some fancier logic here.
+ if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) {
+ foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
+ if (!empty($conn)) {
+ $conn->disconnect();
+ }
+ unset($_DB_DATAOBJECT['CONNECTIONS'][$index]);
+ }
+ }
+
$result = parent::_connect();
if ($result && !$exists) {