summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pym/portage/util/_eventloop/EventLoop.py54
1 files changed, 40 insertions, 14 deletions
diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py
index efd1f1376..b77e8191b 100644
--- a/pym/portage/util/_eventloop/EventLoop.py
+++ b/pym/portage/util/_eventloop/EventLoop.py
@@ -61,6 +61,7 @@ class EventLoop(object):
"""
self._use_signal = main and fcntl is not None
self._thread_rlock = threading.RLock()
+ self._thread_condition = threading.Condition(self._thread_rlock)
self._poll_event_queue = []
self._poll_event_handlers = {}
self._poll_event_handler_ids = {}
@@ -150,7 +151,19 @@ class EventLoop(object):
def iteration(self, *args):
"""
- Like glib.MainContext.iteration(), runs a single iteration.
+ Like glib.MainContext.iteration(), runs a single iteration. In order
+ to avoid blocking forever when may_block is True (the default),
+ callers must be careful to ensure that at least one of the following
+ conditions is met:
+ 1) An event source or timeout is registered which is guaranteed
+ to trigger at least on event (a call to an idle function
+ only counts as an event if it returns a False value which
+ causes it to stop being called)
+ 2) Another thread is guaranteed to call one of the thread-safe
+ methods which notify iteration to stop waiting (such as
+ idle_add or timeout_add).
+ These rules ensure that iteration is able to block until an event
+ arrives, without doing any busy waiting that would waste CPU time.
@type may_block: bool
@param may_block: if True the call may block waiting for an event
(default is True).
@@ -171,19 +184,25 @@ class EventLoop(object):
events_handled = 0
if not event_handlers:
- if self._run_timeouts():
- events_handled += 1
- if not event_handlers and not events_handled and may_block:
- timeout = self._get_poll_timeout()
- if timeout is not None:
+ with self._thread_condition:
+ if self._run_timeouts():
+ events_handled += 1
+ if not event_handlers and not events_handled and may_block:
# Block so that we don't waste cpu time by looping too
# quickly. This makes EventLoop useful for code that needs
# to wait for timeout callbacks regardless of whether or
# not any IO handlers are currently registered.
- try:
- self._poll(timeout=timeout)
- except StopIteration:
- pass
+ timeout = self._get_poll_timeout()
+ if timeout is None:
+ wait_timeout = None
+ else:
+ wait_timeout = float(timeout) / 1000
+ # NOTE: In order to avoid a possible infinite wait when
+ # wait_timeout is None, the previous _run_timeouts()
+ # call must have returned False *with* _thread_condition
+ # acquired. Otherwise, we would risk going to sleep after
+ # our only notify event has already passed.
+ self._thread_condition.wait(wait_timeout)
if self._run_timeouts():
events_handled += 1
@@ -338,16 +357,18 @@ class EventLoop(object):
@rtype: int
@return: an integer ID
"""
- with self._thread_rlock:
+ with self._thread_condition:
source_id = self._new_source_id()
self._idle_callbacks[source_id] = self._idle_callback_class(
args=args, callback=callback, source_id=source_id)
+ self._thread_condition.notify()
return source_id
def _run_idle_callbacks(self):
# assumes caller has acquired self._thread_rlock
if not self._idle_callbacks:
- return
+ return False
+ state_change = 0
# Iterate of our local list, since self._idle_callbacks can be
# modified during the exection of these callbacks.
for x in list(self._idle_callbacks.values()):
@@ -360,10 +381,13 @@ class EventLoop(object):
x.calling = True
try:
if not x.callback(*x.args):
+ state_change += 1
self.source_remove(x.source_id)
finally:
x.calling = False
+ return bool(state_change)
+
def timeout_add(self, interval, function, *args):
"""
Like glib.timeout_add(), interval argument is the number of
@@ -373,7 +397,7 @@ class EventLoop(object):
are passed to your function when it's called. This method is
thread-safe.
"""
- with self._thread_rlock:
+ with self._thread_condition:
source_id = self._new_source_id()
self._timeout_handlers[source_id] = \
self._timeout_handler_class(
@@ -382,6 +406,7 @@ class EventLoop(object):
if self._timeout_interval is None or \
self._timeout_interval > interval:
self._timeout_interval = interval
+ self._thread_condition.notify()
return source_id
def _run_timeouts(self):
@@ -393,7 +418,8 @@ class EventLoop(object):
with self._thread_rlock:
- self._run_idle_callbacks()
+ if self._run_idle_callbacks():
+ calls += 1
if not self._timeout_handlers:
return bool(calls)