summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2012-02-13 17:22:56 -0800
committerZac Medico <zmedico@gentoo.org>2012-02-13 17:22:56 -0800
commit6afd0e508eaf1f9040a20ed670cd6cf7a3a07517 (patch)
treee64842bd08b1e8fc69dd5bce54339b8f947b683a
parentruntests.sh: test PyPy 1.8, and support prefix (diff)
downloadportage-6afd0e508eaf1f9040a20ed670cd6cf7a3a07517.tar.gz
portage-6afd0e508eaf1f9040a20ed670cd6cf7a3a07517.tar.bz2
portage-6afd0e508eaf1f9040a20ed670cd6cf7a3a07517.zip
EventLoop: make _poll/_run_timeouts re-entrant
This fixes infinite loops triggered by Ctrl-C, where timeout calls would exhaust the poll event queue because _poll was not re-entrant. Now, re-entrance is only prohibited for individual callback functions, in order to protect against infinite recursion.
-rw-r--r--pym/portage/util/_eventloop/EventLoop.py135
1 files changed, 73 insertions, 62 deletions
diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py
index 0da388d03..6c6a1b75d 100644
--- a/pym/portage/util/_eventloop/EventLoop.py
+++ b/pym/portage/util/_eventloop/EventLoop.py
@@ -16,13 +16,13 @@ class EventLoop(object):
supports_multiprocessing = True
class _idle_callback_class(SlotObject):
- __slots__ = ("args", "callback", "source_id")
+ __slots__ = ("args", "callback", "calling", "source_id")
class _io_handler_class(SlotObject):
- __slots__ = ("args", "callback", "fd", "source_id")
+ __slots__ = ("args", "callback", "calling", "fd", "source_id")
class _timeout_handler_class(SlotObject):
- __slots__ = ("args", "function", "interval", "source_id",
+ __slots__ = ("args", "function", "calling", "interval", "source_id",
"timestamp")
def __init__(self):
@@ -35,7 +35,6 @@ class EventLoop(object):
self._timeout_handlers = {}
self._timeout_interval = None
self._poll_obj = create_poll_instance()
- self._polling = False
self.IO_ERR = PollConstants.POLLERR
self.IO_HUP = PollConstants.POLLHUP
@@ -45,53 +44,47 @@ class EventLoop(object):
self.IO_PRI = PollConstants.POLLPRI
def _poll(self, timeout=None):
- if self._polling:
- return
- self._polling = True
- try:
- if self._timeout_interval is None:
+ if self._timeout_interval is None:
+ self._run_timeouts()
+ self._do_poll(timeout=timeout)
+
+ elif timeout is None:
+ while True:
self._run_timeouts()
- self._do_poll(timeout=timeout)
+ previous_count = len(self._poll_event_queue)
+ self._do_poll(timeout=self._timeout_interval)
+ if previous_count != len(self._poll_event_queue):
+ break
- elif timeout is None:
- while True:
- self._run_timeouts()
- previous_count = len(self._poll_event_queue)
- self._do_poll(timeout=self._timeout_interval)
- if previous_count != len(self._poll_event_queue):
- break
+ elif timeout <= self._timeout_interval:
+ self._run_timeouts()
+ self._do_poll(timeout=timeout)
- elif timeout <= self._timeout_interval:
+ else:
+ remaining_timeout = timeout
+ start_time = time.time()
+ while True:
self._run_timeouts()
- self._do_poll(timeout=timeout)
-
- else:
- remaining_timeout = timeout
- start_time = time.time()
- while True:
- self._run_timeouts()
- # _timeout_interval can change each time
- # _run_timeouts is called
- min_timeout = remaining_timeout
- if self._timeout_interval is not None and \
- self._timeout_interval < min_timeout:
- min_timeout = self._timeout_interval
-
- previous_count = len(self._poll_event_queue)
- self._do_poll(timeout=min_timeout)
- if previous_count != len(self._poll_event_queue):
- break
- elapsed_time = time.time() - start_time
- if elapsed_time < 0:
- # The system clock has changed such that start_time
- # is now in the future, so just assume that the
- # timeout has already elapsed.
- break
- remaining_timeout = timeout - 1000 * elapsed_time
- if remaining_timeout <= 0:
- break
- finally:
- self._polling = False
+ # _timeout_interval can change each time
+ # _run_timeouts is called
+ min_timeout = remaining_timeout
+ if self._timeout_interval is not None and \
+ self._timeout_interval < min_timeout:
+ min_timeout = self._timeout_interval
+
+ previous_count = len(self._poll_event_queue)
+ self._do_poll(timeout=min_timeout)
+ if previous_count != len(self._poll_event_queue):
+ break
+ elapsed_time = time.time() - start_time
+ if elapsed_time < 0:
+ # The system clock has changed such that start_time
+ # is now in the future, so just assume that the
+ # timeout has already elapsed.
+ break
+ remaining_timeout = timeout - 1000 * elapsed_time
+ if remaining_timeout <= 0:
+ break
def _do_poll(self, timeout=None):
"""
@@ -162,13 +155,8 @@ class EventLoop(object):
events_handled = 0
if not event_handlers:
- if not self._polling:
- self._polling = True
- try:
- if self._run_timeouts():
- events_handled += 1
- finally:
- self._polling = False
+ if self._run_timeouts():
+ events_handled += 1
if not event_handlers:
return bool(events_handled)
@@ -189,9 +177,16 @@ class EventLoop(object):
while event_handlers and self._poll_event_queue:
f, event = self._next_poll_event()
x = event_handlers[f]
- if not x.callback(f, event, *x.args):
- self.source_remove(x.source_id)
+ if x.calling:
+ # don't call it recursively
+ continue
events_handled += 1
+ x.calling = True
+ try:
+ if not x.callback(f, event, *x.args):
+ self.source_remove(x.source_id)
+ finally:
+ x.calling = False
except StopIteration:
events_handled += 1
@@ -223,8 +218,15 @@ class EventLoop(object):
if x.source_id not in self._idle_callbacks:
# it got cancelled while executing another callback
continue
- if not x.callback(*x.args):
- self.source_remove(x.source_id)
+ if x.calling:
+ # don't call it recursively
+ continue
+ x.calling = True
+ try:
+ if not x.callback(*x.args):
+ self.source_remove(x.source_id)
+ finally:
+ x.calling = False
def timeout_add(self, interval, function, *args):
"""
@@ -262,15 +264,24 @@ class EventLoop(object):
# Iterate of our local list, since self._timeout_handlers can be
# modified during the exection of these callbacks.
+ calls = 0
for x in ready_timeouts:
if x.source_id not in self._timeout_handlers:
# it got cancelled while executing another timeout
continue
- x.timestamp = time.time()
- if not x.function(*x.args):
- self.source_remove(x.source_id)
+ if x.calling:
+ # don't call it recursively
+ continue
+ calls += 1
+ x.calling = True
+ try:
+ x.timestamp = time.time()
+ if not x.function(*x.args):
+ self.source_remove(x.source_id)
+ finally:
+ x.calling = False
- return bool(ready_timeouts)
+ return bool(calls)
def io_add_watch(self, f, condition, callback, *args):
"""