Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] _writer: fix unsafe finally clause (bug 728580)
Date: Thu, 18 Jun 2020 07:37:01
Message-Id: 20200618073548.69034-1-zmedico@gentoo.org
1 In the coroutine finally clause, do not call remove_writer in cases
2 where fd has been closed and then re-allocated to a concurrent
3 coroutine as in bug 716636.
4
5 Also, assume that the caller will put the file in non-blocking mode
6 and close the file when done, so that this function is suitable for
7 use within a loop.
8
9 Bug: https://bugs.gentoo.org/728580
10 Signed-off-by: Zac Medico <zmedico@g.o>
11 ---
12 lib/portage/util/futures/_asyncio/process.py | 11 ++++-
13 lib/portage/util/futures/_asyncio/streams.py | 50 ++++++++++----------
14 2 files changed, 33 insertions(+), 28 deletions(-)
15
16 diff --git a/lib/portage/util/futures/_asyncio/process.py b/lib/portage/util/futures/_asyncio/process.py
17 index 020164c9b..2d3e9b0fd 100644
18 --- a/lib/portage/util/futures/_asyncio/process.py
19 +++ b/lib/portage/util/futures/_asyncio/process.py
20 @@ -1,9 +1,12 @@
21 -# Copyright 2018 Gentoo Foundation
22 +# Copyright 2018-2020 Gentoo Authors
23 # Distributed under the terms of the GNU General Public License v2
24
25 +import os
26 +
27 import portage
28 portage.proxy.lazyimport.lazyimport(globals(),
29 'portage.util.futures:asyncio',
30 + 'portage.util.futures.unix_events:_set_nonblocking',
31 )
32 from portage.util.futures._asyncio.streams import _reader, _writer
33 from portage.util.futures.compat_coroutine import coroutine, coroutine_return
34 @@ -59,7 +62,11 @@ class _Process(object):
35 if input is not None:
36 if self._proc.stdin is None:
37 raise TypeError('communicate: expected file or int, got {}'.format(type(self._proc.stdin)))
38 - writer = asyncio.ensure_future(_writer(self._proc.stdin, input), loop=self._loop)
39 + stdin = self._proc.stdin
40 + stdin = os.fdopen(stdin, 'wb', 0) if isinstance(stdin, int) else stdin
41 + _set_nonblocking(stdin.fileno())
42 + writer = asyncio.ensure_future(_writer(stdin, input, loop=self._loop), loop=self._loop)
43 + writer.add_done_callback(lambda writer: stdin.close())
44
45 try:
46 yield asyncio.wait(futures + [self.wait()], loop=self._loop)
47 diff --git a/lib/portage/util/futures/_asyncio/streams.py b/lib/portage/util/futures/_asyncio/streams.py
48 index 650a16491..870307e1e 100644
49 --- a/lib/portage/util/futures/_asyncio/streams.py
50 +++ b/lib/portage/util/futures/_asyncio/streams.py
51 @@ -1,4 +1,4 @@
52 -# Copyright 2018 Gentoo Foundation
53 +# Copyright 2018-2020 Gentoo Authors
54 # Distributed under the terms of the GNU General Public License v2
55
56 import errno
57 @@ -8,7 +8,6 @@ import portage
58 portage.proxy.lazyimport.lazyimport(globals(),
59 '_emerge.PipeReader:PipeReader',
60 'portage.util.futures:asyncio',
61 - 'portage.util.futures.unix_events:_set_nonblocking',
62 )
63 from portage.util.futures.compat_coroutine import coroutine
64
65 @@ -59,38 +58,37 @@ class _Reader(object):
66 @coroutine
67 def _writer(output_file, content, loop=None):
68 """
69 - Asynchronously write bytes to output file, and close it when
70 - done. If an EnvironmentError other than EAGAIN is encountered,
71 - which typically indicates that the other end of the pipe has
72 - close, the error is raised. This function is a coroutine.
73 + Asynchronously write bytes to output file. The output file is
74 + assumed to be in non-blocking mode. If an EnvironmentError
75 + other than EAGAIN is encountered, which typically indicates that
76 + the other end of the pipe has closed, the error is raised.
77 + This function is a coroutine.
78
79 - @param output_file: output file descriptor
80 - @type output_file: file or int
81 + @param output_file: output file
82 + @type output_file: file object
83 @param content: content to write
84 @type content: bytes
85 @param loop: asyncio.AbstractEventLoop (or compatible)
86 @type loop: event loop
87 """
88 - fd = output_file if isinstance(output_file, int) else output_file.fileno()
89 - _set_nonblocking(fd)
90 loop = asyncio._wrap_loop(loop)
91 - try:
92 - while content:
93 + fd = output_file.fileno()
94 + while content:
95 + try:
96 + content = content[os.write(fd, content):]
97 + except EnvironmentError as e:
98 + if e.errno != errno.EAGAIN:
99 + raise
100 waiter = loop.create_future()
101 - loop.add_writer(fd, lambda: waiter.set_result(None))
102 + loop.add_writer(fd, lambda: waiter.done() or waiter.set_result(None))
103 try:
104 yield waiter
105 - while content:
106 - try:
107 - content = content[os.write(fd, content):]
108 - except EnvironmentError as e:
109 - if e.errno == errno.EAGAIN:
110 - break
111 - else:
112 - raise
113 finally:
114 - loop.remove_writer(fd)
115 - except GeneratorExit:
116 - raise
117 - finally:
118 - os.close(output_file) if isinstance(output_file, int) else output_file.close()
119 + # The loop and output file may have been closed.
120 + if not loop.is_closed():
121 + waiter.done() or waiter.cancel()
122 + # Do not call remove_writer in cases where fd has
123 + # been closed and then re-allocated to a concurrent
124 + # coroutine as in bug 716636.
125 + if not output_file.closed:
126 + loop.remove_writer(fd)
127 --
128 2.25.3

Replies