1 |
commit: d9148f4bbba31f23948f300f1d3256feed8defac |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Fri Dec 28 01:35:49 2012 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Fri Dec 28 01:35:49 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=d9148f4b |
7 |
|
8 |
EventLoop.iteration(): avoid busy waiting |
9 |
|
10 |
In order to avoid blocking forever when may_block is True (the |
11 |
default), callers must be careful to ensure that at least one of the |
12 |
following conditions is met: |
13 |
1) An event source or timeout is registered which is guaranteed |
14 |
to trigger at least on event (a call to an idle function |
15 |
only counts as an event if it returns a False value which |
16 |
causes it to be stop being called) |
17 |
2) Another thread is guaranteed to call one of the thread-safe |
18 |
methods which notify iteration to stop waiting (such as |
19 |
idle_add or timeout_add). |
20 |
These rules ensure that iteration is able to block until an event |
21 |
arrives, without doing any busy waiting that would waste CPU time. |
22 |
|
23 |
This will fix busy waiting which would be triggered by |
24 |
PopenPipeBlockingIOTestCase when waiting for the thread from |
25 |
PipeReaderBlockingIO to call idle_add. |
26 |
|
27 |
--- |
28 |
pym/portage/util/_eventloop/EventLoop.py | 54 ++++++++++++++++++++++-------- |
29 |
1 files changed, 40 insertions(+), 14 deletions(-) |
30 |
|
31 |
diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py |
32 |
index efd1f13..fea36c8 100644 |
33 |
--- a/pym/portage/util/_eventloop/EventLoop.py |
34 |
+++ b/pym/portage/util/_eventloop/EventLoop.py |
35 |
@@ -61,6 +61,7 @@ class EventLoop(object): |
36 |
""" |
37 |
self._use_signal = main and fcntl is not None |
38 |
self._thread_rlock = threading.RLock() |
39 |
+ self._thread_condition = threading.Condition(self._thread_rlock) |
40 |
self._poll_event_queue = [] |
41 |
self._poll_event_handlers = {} |
42 |
self._poll_event_handler_ids = {} |
43 |
@@ -150,7 +151,19 @@ class EventLoop(object): |
44 |
|
45 |
def iteration(self, *args): |
46 |
""" |
47 |
- Like glib.MainContext.iteration(), runs a single iteration. |
48 |
+ Like glib.MainContext.iteration(), runs a single iteration. In order |
49 |
+ to avoid blocking forever when may_block is True (the default), |
50 |
+ callers must be careful to ensure that at least one of the following |
51 |
+ conditions is met: |
52 |
+ 1) An event source or timeout is registered which is guaranteed |
53 |
+ to trigger at least on event (a call to an idle function |
54 |
+ only counts as an event if it returns a False value which |
55 |
+ causes it to be stop being called) |
56 |
+ 2) Another thread is guaranteed to call one of the thread-safe |
57 |
+ methods which notify iteration to stop waiting (such as |
58 |
+ idle_add or timeout_add). |
59 |
+ These rules ensure that iteration is able to block until an event |
60 |
+ arrives, without doing any busy waiting that would waste CPU time. |
61 |
@type may_block: bool |
62 |
@param may_block: if True the call may block waiting for an event |
63 |
(default is True). |
64 |
@@ -171,19 +184,25 @@ class EventLoop(object): |
65 |
events_handled = 0 |
66 |
|
67 |
if not event_handlers: |
68 |
- if self._run_timeouts(): |
69 |
- events_handled += 1 |
70 |
- if not event_handlers and not events_handled and may_block: |
71 |
- timeout = self._get_poll_timeout() |
72 |
- if timeout is not None: |
73 |
+ with self._thread_condition: |
74 |
+ if self._run_timeouts(): |
75 |
+ events_handled += 1 |
76 |
+ if not event_handlers and not events_handled and may_block: |
77 |
# Block so that we don't waste cpu time by looping too |
78 |
# quickly. This makes EventLoop useful for code that needs |
79 |
# to wait for timeout callbacks regardless of whether or |
80 |
# not any IO handlers are currently registered. |
81 |
- try: |
82 |
- self._poll(timeout=timeout) |
83 |
- except StopIteration: |
84 |
- pass |
85 |
+ timeout = self._get_poll_timeout() |
86 |
+ if timeout is None: |
87 |
+ wait_timeout = None |
88 |
+ else: |
89 |
+ wait_timeout = float(timeout) / 1000 |
90 |
+ # NOTE: In order to avoid a possible infinite wait when |
91 |
+ # wait_timeout is None, the previous _run_timeouts() |
92 |
+ # call must have returned False *with* _thread_condition |
93 |
+ # acquired. Otherwise, we would risk going to sleep after |
94 |
+ # our only notify even has already passed. |
95 |
+ self._thread_condition.wait(wait_timeout) |
96 |
if self._run_timeouts(): |
97 |
events_handled += 1 |
98 |
|
99 |
@@ -338,16 +357,18 @@ class EventLoop(object): |
100 |
@rtype: int |
101 |
@return: an integer ID |
102 |
""" |
103 |
- with self._thread_rlock: |
104 |
+ with self._thread_condition: |
105 |
source_id = self._new_source_id() |
106 |
self._idle_callbacks[source_id] = self._idle_callback_class( |
107 |
args=args, callback=callback, source_id=source_id) |
108 |
+ self._thread_condition.notify() |
109 |
return source_id |
110 |
|
111 |
def _run_idle_callbacks(self): |
112 |
# assumes caller has acquired self._thread_rlock |
113 |
if not self._idle_callbacks: |
114 |
- return |
115 |
+ return False |
116 |
+ state_change = 0 |
117 |
# Iterate of our local list, since self._idle_callbacks can be |
118 |
# modified during the exection of these callbacks. |
119 |
for x in list(self._idle_callbacks.values()): |
120 |
@@ -360,10 +381,13 @@ class EventLoop(object): |
121 |
x.calling = True |
122 |
try: |
123 |
if not x.callback(*x.args): |
124 |
+ state_change += 1 |
125 |
self.source_remove(x.source_id) |
126 |
finally: |
127 |
x.calling = False |
128 |
|
129 |
+ return bool(state_change) |
130 |
+ |
131 |
def timeout_add(self, interval, function, *args): |
132 |
""" |
133 |
Like glib.timeout_add(), interval argument is the number of |
134 |
@@ -373,7 +397,7 @@ class EventLoop(object): |
135 |
are passed to your function when it's called. This method is |
136 |
thread-safe. |
137 |
""" |
138 |
- with self._thread_rlock: |
139 |
+ with self._thread_condition: |
140 |
source_id = self._new_source_id() |
141 |
self._timeout_handlers[source_id] = \ |
142 |
self._timeout_handler_class( |
143 |
@@ -382,6 +406,7 @@ class EventLoop(object): |
144 |
if self._timeout_interval is None or \ |
145 |
self._timeout_interval > interval: |
146 |
self._timeout_interval = interval |
147 |
+ self._thread_condition.notify() |
148 |
return source_id |
149 |
|
150 |
def _run_timeouts(self): |
151 |
@@ -393,7 +418,8 @@ class EventLoop(object): |
152 |
|
153 |
with self._thread_rlock: |
154 |
|
155 |
- self._run_idle_callbacks() |
156 |
+ if self._run_idle_callbacks(): |
157 |
+ calls += 1 |
158 |
|
159 |
if not self._timeout_handlers: |
160 |
return bool(calls) |