Merge lp:~robru/friends/dont-block-refresh into lp:friends

Proposed by Robert Bruce Park
Status: Merged
Merged at revision: 90
Proposed branch: lp:~robru/friends/dont-block-refresh
Merge into: lp:friends
Diff against target: 78 lines (+27/-16)
2 files modified
friends/service/dispatcher.py (+7/-10)
friends/tests/test_dispatcher.py (+20/-6)
To merge this branch: bzr merge lp:~robru/friends/dont-block-refresh
Reviewer Review Type Date Requested Status
Ken VanDine Approve
Ricardo Mendoza Pending
Review via email: mp+138620@code.launchpad.net

Description of the change

This change prevents Twitter's rate limiter from being able to block the whole MainThread when Dispatcher.Refresh tries to join() all previous threads.

Instead, we now simply abort Dispatcher.Refresh if there is a previous refresh still pending. This approach allows Dispatcher.Refresh to always return immediately, so that it stops blocking the GLib mainloop and the Dee model, without causing any problems with multiple concurrent refreshes stepping on each other's toes, or running afoul of twitter's rate limiter.

To post a comment you must log in.
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Does the main thread counts as 1 in threading.activeCount() ?

Revision history for this message
Robert Bruce Park (robru) wrote :

Yeah, when there is only MainThread and no subthreads, then activeCount() is 1. One single subthread active makes activeCount() equal 2, etc. I doublechecked that in an interactive terminal before writing that code:

>>> import threading
>>> import time
>>> threading.activeCount()
1
>>> threading.Thread(target=lambda:time.sleep(10)).start()
>>> threading.activeCount()
2
>>> time.sleep(10)
>>> threading.activeCount()
1
>>>

Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'friends/service/dispatcher.py'
2--- friends/service/dispatcher.py 2012-12-04 21:06:49 +0000
3+++ friends/service/dispatcher.py 2012-12-07 01:40:26 +0000
4@@ -105,20 +105,17 @@
5 self._unread_count = 0
6
7 log.debug('Refresh requested')
8- # Wait for all previous actions to complete before
9- # starting a load of new ones.
10- current = threading.current_thread()
11- for thread in threading.enumerate():
12- if thread != current:
13- thread.join()
14+
15+ if threading.activeCount() > 1:
16+ log.debug('Aborting refresh because previous refresh incomplete!')
17+ return True
18
19 if not self.online:
20 return
21
22- # account.protocol() starts a new thread for each account present
23- # and returns immediately. So this method should be quick unless
24- # there are a bunch of old, incomplete jobs waiting around from
25- # the last refresh.
26+ # account.protocol() starts a new thread and then returns
27+ # immediately, so there is no delay or blocking during the
28+ # execution of this method.
29 for account in self.account_manager.get_all():
30 try:
31 account.protocol('receive')
32
33=== modified file 'friends/tests/test_dispatcher.py'
34--- friends/tests/test_dispatcher.py 2012-12-04 21:06:49 +0000
35+++ friends/tests/test_dispatcher.py 2012-12-07 01:40:26 +0000
36@@ -70,22 +70,36 @@
37 @mock.patch('friends.service.dispatcher.threading')
38 def test_refresh(self, threading_mock):
39 account = mock.Mock()
40- thread = mock.Mock()
41- threading_mock.enumerate.return_value = [thread]
42- threading_mock.current_thread.return_value = 'MainThread'
43+ threading_mock.activeCount.return_value = 1
44 self.dispatcher.account_manager = mock.Mock()
45 self.dispatcher.account_manager.get_all.return_value = [account]
46
47 self.assertTrue(self.dispatcher.Refresh())
48- threading_mock.current_thread.assert_called_once_with()
49- threading_mock.enumerate.assert_called_once_with()
50- thread.join.assert_called_once_with()
51+ threading_mock.activeCount.assert_called_once_with()
52
53 self.dispatcher.account_manager.get_all.assert_called_once_with()
54 account.protocol.assert_called_once_with('receive')
55
56 self.assertEqual(self.log_mock.empty(), 'Refresh requested\n')
57
58+ @mock.patch('friends.service.dispatcher.threading')
59+ def test_refresh_premature(self, threading_mock):
60+ account = mock.Mock()
61+ threading_mock.activeCount.return_value = 10
62+ self.dispatcher.account_manager = mock.Mock()
63+ self.dispatcher.account_manager.get_all.return_value = [account]
64+
65+ self.assertTrue(self.dispatcher.Refresh())
66+ threading_mock.activeCount.assert_called_once_with()
67+
68+ self.assertEqual(self.dispatcher.account_manager.get_all.call_count, 0)
69+ self.assertEqual(account.protocol.call_count, 0)
70+
71+ self.assertEqual(
72+ self.log_mock.empty(),
73+ 'Refresh requested\n' +
74+ 'Aborting refresh because previous refresh incomplete!\n')
75+
76 def test_clear_indicators(self):
77 self.dispatcher.menu_manager = mock.Mock()
78 self.dispatcher.ClearIndicators()

Subscribers

People subscribed via source and target branches