Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: lib/_emerge/
Date: Fri, 06 Mar 2020 03:04:41
Message-Id: 1583451003.46903f3e5622bc479d4687c76c0e9fada8eb53db.zmedico@gentoo
1 commit: 46903f3e5622bc479d4687c76c0e9fada8eb53db
2 Author: Zac Medico <zmedico <AT> gentoo <DOT> org>
3 AuthorDate: Thu Mar 5 16:45:25 2020 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Thu Mar 5 23:30:03 2020 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=46903f3e
7
8 AsynchronousTask: schedule exit listeners via call_soon (bug 711322)
9
10 Schedule exit listeners via call_soon, in order to avoid callback races
11 like the SequentialTaskQueue exit listener race that triggered bug
12 711322. Callbacks scheduled via call_soon are placed in a fifo queue,
13 ensuring that they execute in an order that is unsurprising relative to
14 other callbacks.
15
16 Bug: https://bugs.gentoo.org/711322
17 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>
18
19 lib/_emerge/AsynchronousTask.py | 53 ++++++++++++++++-------------------------
20 1 file changed, 21 insertions(+), 32 deletions(-)
21
22 diff --git a/lib/_emerge/AsynchronousTask.py b/lib/_emerge/AsynchronousTask.py
23 index 1b450e3b0..799e66a4a 100644
24 --- a/lib/_emerge/AsynchronousTask.py
25 +++ b/lib/_emerge/AsynchronousTask.py
26 @@ -19,7 +19,7 @@ class AsynchronousTask(SlotObject):
27 """
28
29 __slots__ = ("background", "cancelled", "returncode", "scheduler") + \
30 - ("_exit_listeners", "_exit_listener_stack", "_start_listeners")
31 + ("_exit_listener_handles", "_exit_listeners", "_start_listeners")
32
33 _cancelled_returncode = - signal.SIGINT
34
35 @@ -178,17 +178,16 @@ class AsynchronousTask(SlotObject):
36 self._exit_listeners.append(f)
37
38 def removeExitListener(self, f):
39 - if self._exit_listeners is None:
40 - if self._exit_listener_stack is not None:
41 - try:
42 - self._exit_listener_stack.remove(f)
43 - except ValueError:
44 - pass
45 - return
46 - try:
47 - self._exit_listeners.remove(f)
48 - except ValueError:
49 - pass
50 + if self._exit_listeners is not None:
51 + try:
52 + self._exit_listeners.remove(f)
53 + except ValueError:
54 + pass
55 +
56 + if self._exit_listener_handles is not None:
57 + handle = self._exit_listener_handles.pop(f, None)
58 + if handle is not None:
59 + handle.cancel()
60
61 def _wait_hook(self):
62 """
63 @@ -200,26 +199,16 @@ class AsynchronousTask(SlotObject):
64 if self.returncode is not None and \
65 self._exit_listeners is not None:
66
67 - # This prevents recursion, in case one of the
68 - # exit handlers triggers this method again by
69 - # calling wait(). Use a stack that gives
70 - # removeExitListener() an opportunity to consume
71 - # listeners from the stack, before they can get
72 - # called below. This is necessary because a call
73 - # to one exit listener may result in a call to
74 - # removeExitListener() for another listener on
75 - # the stack. That listener needs to be removed
76 - # from the stack since it would be inconsistent
77 - # to call it after it has been been passed into
78 - # removeExitListener().
79 - self._exit_listener_stack = self._exit_listeners
80 + listeners = self._exit_listeners
81 self._exit_listeners = None
82 + if self._exit_listener_handles is None:
83 + self._exit_listener_handles = {}
84
85 - # Execute exit listeners in reverse order, so that
86 - # the last added listener is executed first. This
87 - # allows SequentialTaskQueue to decrement its running
88 - # task count as soon as one of its tasks exits, so that
89 - # the value is accurate when other listeners execute.
90 - while self._exit_listener_stack:
91 - self._exit_listener_stack.pop()(self)
92 + for listener in listeners:
93 + if listener not in self._exit_listener_handles:
94 + self._exit_listener_handles[listener] = \
95 + self.scheduler.call_soon(self._exit_listener_cb, listener)
96
97 + def _exit_listener_cb(self, listener):
98 + del self._exit_listener_handles[listener]
99 + listener(self)