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) |