You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/05/29 18:38:59 UTC
[1/2] git commit: [#4994] Improved error checking around
notifications to prevent lost notifications
Updated Branches:
refs/heads/cj/4994 [created] 7ab6dc9fd
[#4994] Improved error checking around notifications to prevent lost notifications
Signed-off-by: Cory Johns <cj...@slashdotmedia.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/7c8e7f39
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/7c8e7f39
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/7c8e7f39
Branch: refs/heads/cj/4994
Commit: 7c8e7f3903510bf4fcb138e5042c43958e5792b2
Parents: 82f675f
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed May 29 15:44:24 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 29 15:44:24 2013 +0000
----------------------------------------------------------------------
Allura/allura/model/notification.py | 67 +++++++++++++++++++++---------
1 files changed, 47 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7c8e7f39/Allura/allura/model/notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 3c3517f..2c700a7 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -454,13 +454,20 @@ class Mailbox(MappedClass):
'topic':{'$in':[None, topic]}
}
for mbox in cls.query.find(d):
- mbox.query.update(
- {'$push':dict(queue=nid),
- '$set':dict(last_modified=datetime.utcnow(),
- queue_empty=False),
- })
- # Make sure the mbox doesn't stick around to be flush()ed
- session(mbox).expunge(mbox)
+ try:
+ mbox.query.update(
+ {'$push':dict(queue=nid),
+ '$set':dict(last_modified=datetime.utcnow(),
+ queue_empty=False),
+ })
+ # Make sure the mbox doesn't stick around to be flush()ed
+ session(mbox).expunge(mbox)
+ except:
+ # log error but try to keep processing, lest all the other eligible
+ # mboxes for this notification get skipped and lost forever
+ log.exception(
+ 'Error adding notification %s for artifact %s on project %s to user %s',
+ nid, artifact_index_id, c.project._id, mbox.user_id)
@classmethod
def fire_ready(cls):
@@ -490,7 +497,11 @@ class Mailbox(MappedClass):
new=False)
for mbox in take_while_true(find_and_modify_direct_mbox):
- mbox.fire(now)
+ try:
+ mbox.fire(now)
+ except:
+ log.exception('Error firing mbox for user %s with queue: [%s]', mbox.user_id, ', '.join(mbox.queue))
+ raise # re-raise so we don't keep (destructively) trying to process mboxes
for mbox in cls.query.find(q_digest):
next_scheduled = now
@@ -519,20 +530,36 @@ class Mailbox(MappedClass):
if self.type == 'direct':
ngroups = defaultdict(list)
for n in notifications:
- if n.topic == 'message':
- n.send_direct(self.user_id)
- # Messages must be sent individually so they can be replied
- # to individually
- else:
- key = (n.subject, n.from_address, n.reply_to_address, n.author_id)
- ngroups[key].append(n)
+ try:
+ if n.topic == 'message':
+ n.send_direct(self.user_id)
+ # Messages must be sent individually so they can be replied
+ # to individually
+ else:
+ key = (n.subject, n.from_address, n.reply_to_address, n.author_id)
+ ngroups[key].append(n)
+ except:
+ # log error but keep trying to deliver other notifications,
+ # lest the other notifications (which have already been removed
+ # from the mobx's queue in mongo) be lost
+ log.exception(
+ 'Error sending notification %s to mbox %s (user %s)',
+ n._id, self._id, self.user_id)
# Accumulate messages from same address with same subject
for (subject, from_address, reply_to_address, author_id), ns in ngroups.iteritems():
- if len(ns) == 1:
- n.send_direct(self.user_id)
- else:
- Notification.send_digest(
- self.user_id, from_address, subject, ns, reply_to_address)
+ try:
+ if len(ns) == 1:
+ n.send_direct(self.user_id)
+ else:
+ Notification.send_digest(
+ self.user_id, from_address, subject, ns, reply_to_address)
+ except:
+ # log error but keep trying to deliver other notifications,
+ # lest the other notifications (which have already been removed
+ # from the mobx's queue in mongo) be lost
+ log.exception(
+ 'Error sending notifications [%s] to mbox %s (user %s)',
+ ', '.join([n._id for n in ns]), self._id, self.user_id)
elif self.type == 'digest':
Notification.send_digest(
self.user_id, u'noreply@in.sf.net', 'Digest Email',
[2/2] git commit: [#4994] Fixed bug in direct notification
accumulation that could cause missing notifications
Posted by jo...@apache.org.
[#4994] Fixed bug in direct notification accumulation that could cause missing notifications
Signed-off-by: Cory Johns <cj...@slashdotmedia.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/7ab6dc9f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/7ab6dc9f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/7ab6dc9f
Branch: refs/heads/cj/4994
Commit: 7ab6dc9fd4b574f827e380246fc5938e5e1989c5
Parents: 7c8e7f3
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed May 29 16:38:28 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 29 16:38:28 2013 +0000
----------------------------------------------------------------------
Allura/allura/model/notification.py | 2 +-
Allura/allura/tests/model/test_notification.py | 40 +++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7ab6dc9f/Allura/allura/model/notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 2c700a7..946fea0 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -549,7 +549,7 @@ class Mailbox(MappedClass):
for (subject, from_address, reply_to_address, author_id), ns in ngroups.iteritems():
try:
if len(ns) == 1:
- n.send_direct(self.user_id)
+ ns[0].send_direct(self.user_id)
else:
Notification.send_digest(
self.user_id, from_address, subject, ns, reply_to_address)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7ab6dc9f/Allura/allura/tests/model/test_notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index a894e20..6cdfbbb 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -17,10 +17,13 @@
import unittest
from datetime import timedelta
+import collections
from pylons import tmpl_context as c, app_globals as g
from nose.tools import assert_equal, assert_in
from ming.orm import ThreadLocalORMSession
+import mock
+import bson
from alluratest.controller import setup_basic_test, setup_global_objects, REGISTRY
from allura import model as M
@@ -258,6 +261,43 @@ class TestSubscriptionTypes(unittest.TestCase):
def _post_notification(self, text=None):
return M.Notification.post(self.pg, 'metadata', text=text)
+ @mock.patch('allura.model.notification.defaultdict')
+ @mock.patch('allura.model.notification.Notification')
+ def test_direct_accumulation(self, mocked_notification, mocked_defaultdict):
+ class OrderedDefaultDict(collections.OrderedDict):
+ def __init__(self, factory=list, *a, **kw):
+ self._factory = factory
+ super(OrderedDefaultDict, self).__init__(*a, **kw)
+
+ def __getitem__(self, key):
+ if key not in self:
+ value = self[key] = self._factory()
+ else:
+ value = super(OrderedDefaultDict, self).__getitem__(key)
+ return value
+
+ notifications = mocked_notification.query.find.return_value.all.return_value = [
+ mock.Mock(_id='n0', topic='metadata', subject='s1', from_address='f1', reply_to_address='rt1', author_id='a1'),
+ mock.Mock(_id='n1', topic='metadata', subject='s2', from_address='f2', reply_to_address='rt2', author_id='a2'),
+ mock.Mock(_id='n2', topic='metadata', subject='s2', from_address='f2', reply_to_address='rt2', author_id='a2'),
+ mock.Mock(_id='n3', topic='message', subject='s3', from_address='f3', reply_to_address='rt3', author_id='a3'),
+ mock.Mock(_id='n4', topic='message', subject='s3', from_address='f3', reply_to_address='rt3', author_id='a3'),
+ ]
+ mocked_defaultdict.side_effect = OrderedDefaultDict
+
+ u0 = bson.ObjectId()
+ mbox = M.Mailbox(type='direct', user_id=u0, queue=['n0', 'n1', 'n2', 'n3', 'n4'])
+ mbox.fire('now')
+
+ mocked_notification.query.find.assert_called_once_with({'_id': {'$in': ['n0', 'n1', 'n2', 'n3', 'n4']}})
+ # first notification should be sent direct, as its key values are unique
+ notifications[0].send_direct.assert_called_once_with(u0)
+ # next two notifications should be sent as a digest as they have matching key values
+ mocked_notification.send_digest.assert_called_once_with(u0, 'f2', 's2', [notifications[1], notifications[2]], 'rt2')
+ # final two should be sent direct even though they matching keys, as they are messages
+ notifications[3].send_direct.assert_called_once_with(u0)
+ notifications[4].send_direct.assert_called_once_with(u0)
+
def _clear_subscriptions():
M.Mailbox.query.remove({})