From ab7a06542c73b6bcd903d0184bd1cf4ac494c560 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 10 Dec 2010 22:08:36 +0000 Subject: [PATCH 1/3] Workaround for locally-handled sessions breaking on PHP 5.3 with APC enabled. Big thanks to the folks at http://pecl.php.net/bugs/bug.php?id=16745 for the secret juju! Classes were being torn down before session save handlers got called at the end of the request, which exploded with complaints about being unable to find various classes. Registering a shutdown function lets us explicitly close out the session before everything gets torn down. --- classes/Session.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/classes/Session.php b/classes/Session.php index 2422f8b68e..e1c83ad4dc 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -178,6 +178,18 @@ class Session extends Memcached_DataObject $result = session_set_save_handler('Session::open', 'Session::close', 'Session::read', 'Session::write', 'Session::destroy', 'Session::gc'); self::logdeb("save handlers result = $result"); + + // PHP 5.3 with APC ends up destroying a bunch of object stuff before the session + // save handlers get called on request teardown. + // Registering an explicit shutdown function should take care of this before + // everything breaks on us. + register_shutdown_function('Session::cleanup'); + return $result; } + + static function cleanup() + { + session_write_close(); + } } From 7285bbc93b5394a16731e498b62188ea847de38d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 11 Dec 2010 10:24:46 -0500 Subject: [PATCH 2/3] Subscription stream functions Made two new functions, Subscription::bySubscriber() and Subscription::bySubscribed(), to get streams of Subscription objects. Converted Profile::getSubscribers() and Profile::getSubscriptions() to use these functions. --- classes/Profile.php | 54 ++++++++++----------------------- classes/Subscription.php | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 8dbdcbd973..239c368ca1 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -380,54 +380,32 @@ class Profile extends Memcached_DataObject function getSubscriptions($offset=0, $limit=null) { - $qry = - 'SELECT profile.* ' . - 'FROM profile JOIN subscription ' . - 'ON profile.id = subscription.subscribed ' . - 'WHERE subscription.subscriber = %d ' . - 'AND subscription.subscribed != subscription.subscriber ' . - 'ORDER BY subscription.created DESC '; + $subs = Subscription::bySubscriber($this->id, + $offset, + $limit); - if ($offset>0 && !is_null($limit)){ - if (common_config('db','type') == 'pgsql') { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } else { - $qry .= ' LIMIT ' . $offset . ', ' . $limit; - } + $profiles = array(); + + while ($subs->fetch()) { + $profiles[] = Profile::staticGet($subs->subscribed); } - $profile = new Profile(); - - $profile->query(sprintf($qry, $this->id)); - - return $profile; + return new ArrayWrapper($profiles); } function getSubscribers($offset=0, $limit=null) { - $qry = - 'SELECT profile.* ' . - 'FROM profile JOIN subscription ' . - 'ON profile.id = subscription.subscriber ' . - 'WHERE subscription.subscribed = %d ' . - 'AND subscription.subscribed != subscription.subscriber ' . - 'ORDER BY subscription.created DESC '; + $subs = Subscription::bySubscribed($this->id, + $offset, + $limit); - if ($offset>0 && !is_null($limit)){ - if ($offset) { - if (common_config('db','type') == 'pgsql') { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } else { - $qry .= ' LIMIT ' . $offset . ', ' . $limit; - } - } + $profiles = array(); + + while ($subs->fetch()) { + $profiles[] = Profile::staticGet($subs->subscriber); } - $profile = new Profile(); - - $cnt = $profile->query(sprintf($qry, $this->id)); - - return $profile; + return new ArrayWrapper($profiles); } function getConnectedApps($offset = 0, $limit = null) diff --git a/classes/Subscription.php b/classes/Subscription.php index e9ad2a5a20..a4764e9f15 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -264,4 +264,68 @@ class Subscription extends Memcached_DataObject return $act; } + + /** + * Stream of subscriptions with the same subscriber + * + * Useful for showing pages that list subscriptions in reverse + * chronological order. Has offset & limit to make paging + * easy. + * + * @param integer $subscriberId Profile ID of the subscriber + * @param integer $offset Offset from latest + * @param integer $limit Maximum number to fetch + * + * @return Subscription stream of subscriptions; use fetch() to iterate + */ + + static function bySubscriber($subscriberId, + $offset = 0, + $limit = PROFILES_PER_PAGE) + { + $sub = new Subscription(); + + $sub->subscriber = $subscriberId; + + $sub->whereAdd('subscribed != ' . $subscriberId); + + $sub->orderBy('created DESC'); + $sub->limit($offset, $limit); + + $sub->find(); + + return $sub; + } + + /** + * Stream of subscriptions with the same subscribed profile + * + * Useful for showing pages that list subscribers in reverse + * chronological order. Has offset & limit to make paging + * easy. + * + * @param integer $subscribedId Profile ID of the subscribed + * @param integer $offset Offset from latest + * @param integer $limit Maximum number to fetch + * + * @return Subscription stream of subscriptions; use fetch() to iterate + */ + + static function bySubscribed($subscribedId, + $offset = 0, + $limit = PROFILES_PER_PAGE) + { + $sub = new Subscription(); + + $sub->subscribed = $subscribedId; + + $sub->whereAdd('subscriber != ' . $subscribedId); + + $sub->orderBy('created DESC'); + $sub->limit($offset, $limit); + + $sub->find(); + + return $sub; + } } From d0ea138888cbc5de30f2c9a52a771921efa9fdc1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 11 Dec 2010 11:00:04 -0500 Subject: [PATCH 3/3] cache stream of subscriptions --- classes/Subscription.php | 91 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/classes/Subscription.php b/classes/Subscription.php index a4764e9f15..763e3835b8 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -26,6 +26,8 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Subscription extends Memcached_DataObject { + const CACHE_WINDOW = 201; + ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -91,6 +93,9 @@ class Subscription extends Memcached_DataObject self::blow('user:notices_with_friends:%d', $subscriber->id); + self::blow('subscription:by-subscriber:'.$subscriber->id); + self::blow('subscription:by-subscribed:'.$other->id); + $subscriber->blowSubscriptionCount(); $other->blowSubscriberCount(); @@ -220,6 +225,9 @@ class Subscription extends Memcached_DataObject self::blow('user:notices_with_friends:%d', $subscriber->id); + self::blow('subscription:by-subscriber:'.$subscriber->id); + self::blow('subscription:by-subscribed:'.$other->id); + $subscriber->blowSubscriptionCount(); $other->blowSubscriberCount(); @@ -282,6 +290,29 @@ class Subscription extends Memcached_DataObject static function bySubscriber($subscriberId, $offset = 0, $limit = PROFILES_PER_PAGE) + { + if ($offset + $limit > self::CACHE_WINDOW) { + return new ArrayWrapper(self::realBySubscriber($subscriberId, + $offset, + $limit)); + } else { + $key = 'subscription:by-subscriber:'.$subscriberId; + $window = self::cacheGet($key); + if ($window === false) { + $window = self::realBySubscriber($subscriberId, + 0, + self::CACHE_WINDOW); + self::cacheSet($key, $window); + } + return new ArrayWrapper(array_slice($window, + $offset, + $limit)); + } + } + + private static function realBySubscriber($subscriberId, + $offset, + $limit) { $sub = new Subscription(); @@ -294,7 +325,13 @@ class Subscription extends Memcached_DataObject $sub->find(); - return $sub; + $subs = array(); + + while ($sub->fetch()) { + $subs[] = clone($sub); + } + + return $subs; } /** @@ -314,6 +351,29 @@ class Subscription extends Memcached_DataObject static function bySubscribed($subscribedId, $offset = 0, $limit = PROFILES_PER_PAGE) + { + if ($offset + $limit > self::CACHE_WINDOW) { + return new ArrayWrapper(self::realBySubscribed($subscribedId, + $offset, + $limit)); + } else { + $key = 'subscription:by-subscribed:'.$subscribedId; + $window = self::cacheGet($key); + if ($window === false) { + $window = self::realBySubscribed($subscribedId, + 0, + self::CACHE_WINDOW); + self::cacheSet($key, $window); + } + return new ArrayWrapper(array_slice($window, + $offset, + $limit)); + } + } + + private static function realBySubscribed($subscribedId, + $offset, + $limit) { $sub = new Subscription(); @@ -326,6 +386,33 @@ class Subscription extends Memcached_DataObject $sub->find(); - return $sub; + $subs = array(); + + while ($sub->fetch()) { + $subs[] = clone($sub); + } + + return $subs; + } + + /** + * Flush cached subscriptions when subscription is updated + * + * Because we cache subscriptions, it's useful to flush them + * here. + * + * @param mixed $orig Original version of object + * + * @return boolean success flag. + */ + + function update($orig=null) + { + $result = parent::update($orig); + + self::blow('subscription:by-subscriber:'.$this->subscriber); + self::blow('subscription:by-subscribed:'.$this->subscribed); + + return $result; } }