1 |
commit: eaf22a6d88ad8e0b7a3a1e21f3234c6b7037018a |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Mon Mar 27 06:44:02 2017 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Mon Mar 27 21:40:38 2017 +0000 |
6 |
URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=eaf22a6d |
7 |
|
8 |
SpawnProcess: fix event loop recursion in _pipe_logger_exit (bug 613990) |
9 |
|
10 |
Fix SpawnProcess._pipe_logger_exit to wait for process exit status |
11 |
asynchronously, in order to avoid event loop recursion. This is |
12 |
required for asyncio compatibility, and also protects emerge from |
13 |
exceeding the maximum recursion depth limit like in bug 402335. |
14 |
|
15 |
X-Gentoo-bug: 613990 |
16 |
X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=613990 |
17 |
Acked-by: Brian Dolbec <dolsen <AT> gentoo.org> |
18 |
|
19 |
pym/_emerge/SpawnProcess.py | 3 +-- |
20 |
pym/_emerge/SubProcess.py | 23 ++++++++++++++++++++++- |
21 |
2 files changed, 23 insertions(+), 3 deletions(-) |
22 |
|
23 |
diff --git a/pym/_emerge/SpawnProcess.py b/pym/_emerge/SpawnProcess.py |
24 |
index e046640ea..7326254ca 100644 |
25 |
--- a/pym/_emerge/SpawnProcess.py |
26 |
+++ b/pym/_emerge/SpawnProcess.py |
27 |
@@ -170,8 +170,7 @@ class SpawnProcess(SubProcess): |
28 |
|
29 |
def _pipe_logger_exit(self, pipe_logger): |
30 |
self._pipe_logger = None |
31 |
- self._unregister() |
32 |
- self.wait() |
33 |
+ self._async_waitpid() |
34 |
|
35 |
def _waitpid_loop(self): |
36 |
SubProcess._waitpid_loop(self) |
37 |
|
38 |
diff --git a/pym/_emerge/SubProcess.py b/pym/_emerge/SubProcess.py |
39 |
index 13d938297..b81cfd5f6 100644 |
40 |
--- a/pym/_emerge/SubProcess.py |
41 |
+++ b/pym/_emerge/SubProcess.py |
42 |
@@ -12,7 +12,7 @@ import errno |
43 |
class SubProcess(AbstractPollTask): |
44 |
|
45 |
__slots__ = ("pid",) + \ |
46 |
- ("_dummy_pipe_fd", "_files", "_reg_id") |
47 |
+ ("_dummy_pipe_fd", "_files", "_reg_id", "_waitpid_id") |
48 |
|
49 |
# This is how much time we allow for waitpid to succeed after |
50 |
# we've sent a kill signal to our subprocess. |
51 |
@@ -101,6 +101,23 @@ class SubProcess(AbstractPollTask): |
52 |
|
53 |
return self.returncode |
54 |
|
55 |
+ def _async_waitpid(self): |
56 |
+ """ |
57 |
+ Wait for exit status of self.pid asynchronously, and then |
58 |
+ set the returncode and notify exit listeners. This is |
59 |
+ prefered over _waitpid_loop, since the synchronous nature |
60 |
+ of _waitpid_loop can cause event loop recursion. |
61 |
+ """ |
62 |
+ if self._waitpid_id is None: |
63 |
+ self._waitpid_id = self.scheduler.child_watch_add( |
64 |
+ self.pid, self._async_waitpid_cb) |
65 |
+ |
66 |
+ def _async_waitpid_cb(self, pid, condition, user_data=None): |
67 |
+ if pid != self.pid: |
68 |
+ raise AssertionError("expected pid %s, got %s" % (self.pid, pid)) |
69 |
+ self._set_returncode((pid, condition)) |
70 |
+ self.wait() |
71 |
+ |
72 |
def _waitpid_loop(self): |
73 |
source_id = self.scheduler.child_watch_add( |
74 |
self.pid, self._waitpid_cb) |
75 |
@@ -129,6 +146,10 @@ class SubProcess(AbstractPollTask): |
76 |
self.scheduler.source_remove(self._reg_id) |
77 |
self._reg_id = None |
78 |
|
79 |
+ if self._waitpid_id is not None: |
80 |
+ self.scheduler.source_remove(self._waitpid_id) |
81 |
+ self._waitpid_id = None |
82 |
+ |
83 |
if self._files is not None: |
84 |
for f in self._files.values(): |
85 |
if isinstance(f, int): |