Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/util/_eventloop/
Date: Fri, 28 Dec 2012 01:44:53
Message-Id: 1356659067.a0f22daa7cf359aac776a45bbc60d22dcd947034.zmedico@gentoo
1 commit: a0f22daa7cf359aac776a45bbc60d22dcd947034
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:44:27 2012 +0000
6 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=a0f22daa
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..b77e819 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 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 event 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)