commit:     b443b87c5397867c287f3bc4c5f1f4fa5e234d0a
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Wed Apr 18 21:38:33 2018 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Wed Apr 18 21:49:42 2018 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=b443b87c

EventLoop._run_idle_callbacks: support recursive calls (bug 653508)

Since recursive calls must be supported until all consumers of the
AsynchronousLock.unlock() method have been migrated to use the
async_unlock() method (bug 614108.), support recursive calls by using
a self._idle_callbacks_remaining loop control variable that is reset
by each recursive call.

Fixes: 9772f8f2a58a (EventLoop._idle_add: use thread-safe deque append)
Bug: https://bugs.gentoo.org/653508

 pym/portage/util/_eventloop/EventLoop.py | 60 ++++++++++++++------------------
 1 file changed, 26 insertions(+), 34 deletions(-)

diff --git a/pym/portage/util/_eventloop/EventLoop.py 
b/pym/portage/util/_eventloop/EventLoop.py
index 895303699..7208c3aa1 100644
--- a/pym/portage/util/_eventloop/EventLoop.py
+++ b/pym/portage/util/_eventloop/EventLoop.py
@@ -145,7 +145,7 @@ class EventLoop(object):
                # Use deque, with thread-safe append, in order to emulate the 
FIFO
                # queue behavior of the AbstractEventLoop.call_soon method.
                self._idle_callbacks = collections.deque()
-               self._idle_callbacks_running = False
+               self._idle_callbacks_remaining = 0
                self._timeout_handlers = {}
                self._timeout_interval = None
                self._default_executor = None
@@ -534,26 +534,29 @@ class EventLoop(object):
                reschedule = []
                # Use remaining count to avoid calling any newly scheduled 
callbacks,
                # since self._idle_callbacks can be modified during the 
exection of
-               # these callbacks.
-               remaining = len(self._idle_callbacks)
-               try:
-                       while remaining:
-                               remaining -= 1
-                               try:
-                                       x = self._idle_callbacks.popleft() # 
thread-safe
-                               except IndexError:
-                                       break
-                               if x._cancelled:
-                                       # it got cancelled while executing 
another callback
-                                       continue
-                               if x._callback(*x._args):
-                                       reschedule.append(x)
-                               else:
-                                       x._cancelled = True
-                                       state_change += 1
-               finally:
-                       # Reschedule those that were not cancelled.
-                       self._idle_callbacks.extend(reschedule)
+               # these callbacks. The remaining count can be reset by recursive
+               # calls to this method. Recursion must remain supported until 
all
+               # consumers of AsynchronousLock.unlock() have been migrated to 
the
+               # async_unlock() method, see bug 614108.
+               self._idle_callbacks_remaining = len(self._idle_callbacks)
+
+               while self._idle_callbacks_remaining:
+                       self._idle_callbacks_remaining -= 1
+                       try:
+                               x = self._idle_callbacks.popleft() # thread-safe
+                       except IndexError:
+                               break
+                       if x._cancelled:
+                               # it got cancelled while executing another 
callback
+                               continue
+                       if x._callback(*x._args):
+                               # Reschedule, but not until after it's called, 
since
+                               # we don't want it to call itself in a 
recursive call
+                               # to this method.
+                               self._idle_callbacks.append(x)
+                       else:
+                               x._cancelled = True
+                               state_change += 1
 
                return bool(state_change)
 
@@ -587,19 +590,8 @@ class EventLoop(object):
 
                with self._thread_rlock:
 
-                       if self._idle_callbacks_running:
-                               # The caller should use call_soon in order to
-                               # prevent recursion here. Raise an error because
-                               # _run_idle_callbacks has an internal remaining
-                               # count that recursion would render meaningless.
-                               raise AssertionError('idle callback recursion')
-
-                       self._idle_callbacks_running = True
-                       try:
-                               if self._run_idle_callbacks():
-                                       calls += 1
-                       finally:
-                               self._idle_callbacks_running = False
+                       if self._run_idle_callbacks():
+                               calls += 1
 
                        if not self._timeout_handlers:
                                return bool(calls)

Reply via email to