summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2015-01-14 01:10:33 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2015-01-14 01:10:33 (GMT)
commitf651a604075c2dc9a2d7f3d3bd74da374ff8a696 (patch)
tree1fd5b58dcc596b6d4b26b9904076f391040f4f3e
parentc2c12e433aa47149c692eef5e5debd7c475b04c7 (diff)
downloadcpython-f651a604075c2dc9a2d7f3d3bd74da374ff8a696.zip
cpython-f651a604075c2dc9a2d7f3d3bd74da374ff8a696.tar.gz
cpython-f651a604075c2dc9a2d7f3d3bd74da374ff8a696.tar.bz2
Python issue #23173: sync with Tulip
* If an exception is raised during the creation of a subprocess, kill the subprocess (close pipes, kill and read the return status). Log an error in such case. * Fix SubprocessStreamProtocol.connection_made() to handle cancelled waiter. Add unit test cancelling subprocess methods.
-rw-r--r--Lib/asyncio/base_subprocess.py77
-rw-r--r--Lib/asyncio/subprocess.py16
-rw-r--r--Lib/test/test_asyncio/test_subprocess.py36
3 files changed, 102 insertions, 27 deletions
diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
index afc434d..0787ad7 100644
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -96,32 +96,61 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
def kill(self):
self._proc.kill()
+ def _kill_wait(self):
+ """Close pipes, kill the subprocess and read its return status.
+
+ Function called when an exception is raised during the creation
+ of a subprocess.
+ """
+ if self._loop.get_debug():
+ logger.warning('Exception during subprocess creation, '
+ 'kill the subprocess %r',
+ self,
+ exc_info=True)
+
+ proc = self._proc
+ if proc.stdout:
+ proc.stdout.close()
+ if proc.stderr:
+ proc.stderr.close()
+ if proc.stdin:
+ proc.stdin.close()
+ try:
+ proc.kill()
+ except ProcessLookupError:
+ pass
+ proc.wait()
+
@coroutine
def _post_init(self):
- proc = self._proc
- loop = self._loop
- if proc.stdin is not None:
- _, pipe = yield from loop.connect_write_pipe(
- lambda: WriteSubprocessPipeProto(self, 0),
- proc.stdin)
- self._pipes[0] = pipe
- if proc.stdout is not None:
- _, pipe = yield from loop.connect_read_pipe(
- lambda: ReadSubprocessPipeProto(self, 1),
- proc.stdout)
- self._pipes[1] = pipe
- if proc.stderr is not None:
- _, pipe = yield from loop.connect_read_pipe(
- lambda: ReadSubprocessPipeProto(self, 2),
- proc.stderr)
- self._pipes[2] = pipe
-
- assert self._pending_calls is not None
-
- self._loop.call_soon(self._protocol.connection_made, self)
- for callback, data in self._pending_calls:
- self._loop.call_soon(callback, *data)
- self._pending_calls = None
+ try:
+ proc = self._proc
+ loop = self._loop
+ if proc.stdin is not None:
+ _, pipe = yield from loop.connect_write_pipe(
+ lambda: WriteSubprocessPipeProto(self, 0),
+ proc.stdin)
+ self._pipes[0] = pipe
+ if proc.stdout is not None:
+ _, pipe = yield from loop.connect_read_pipe(
+ lambda: ReadSubprocessPipeProto(self, 1),
+ proc.stdout)
+ self._pipes[1] = pipe
+ if proc.stderr is not None:
+ _, pipe = yield from loop.connect_read_pipe(
+ lambda: ReadSubprocessPipeProto(self, 2),
+ proc.stderr)
+ self._pipes[2] = pipe
+
+ assert self._pending_calls is not None
+
+ self._loop.call_soon(self._protocol.connection_made, self)
+ for callback, data in self._pending_calls:
+ self._loop.call_soon(callback, *data)
+ self._pending_calls = None
+ except:
+ self._kill_wait()
+ raise
def _call(self, cb, *data):
if self._pending_calls is not None:
diff --git a/Lib/asyncio/subprocess.py b/Lib/asyncio/subprocess.py
index a8ad03c..a028339 100644
--- a/Lib/asyncio/subprocess.py
+++ b/Lib/asyncio/subprocess.py
@@ -60,7 +60,9 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
protocol=self,
reader=None,
loop=self._loop)
- self.waiter.set_result(None)
+
+ if not self.waiter.cancelled():
+ self.waiter.set_result(None)
def pipe_data_received(self, fd, data):
if fd == 1:
@@ -216,7 +218,11 @@ def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
protocol_factory,
cmd, stdin=stdin, stdout=stdout,
stderr=stderr, **kwds)
- yield from protocol.waiter
+ try:
+ yield from protocol.waiter
+ except:
+ transport._kill_wait()
+ raise
return Process(transport, protocol, loop)
@coroutine
@@ -232,5 +238,9 @@ def create_subprocess_exec(program, *args, stdin=None, stdout=None,
program, *args,
stdin=stdin, stdout=stdout,
stderr=stderr, **kwds)
- yield from protocol.waiter
+ try:
+ yield from protocol.waiter
+ except:
+ transport._kill_wait()
+ raise
return Process(transport, protocol, loop)
diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
index 5fc1dc0..b2f1b95 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -251,6 +251,42 @@ class SubprocessMixin:
self.loop.run_until_complete(cancel_wait())
+ def test_cancel_make_subprocess_transport_exec(self):
+ @asyncio.coroutine
+ def cancel_make_transport():
+ coro = asyncio.create_subprocess_exec(*PROGRAM_BLOCKED,
+ loop=self.loop)
+ task = self.loop.create_task(coro)
+
+ self.loop.call_soon(task.cancel)
+ try:
+ yield from task
+ except asyncio.CancelledError:
+ pass
+
+ # ignore the log:
+ # "Exception during subprocess creation, kill the subprocess"
+ with test_utils.disable_logger():
+ self.loop.run_until_complete(cancel_make_transport())
+
+ def test_cancel_post_init(self):
+ @asyncio.coroutine
+ def cancel_make_transport():
+ coro = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
+ *PROGRAM_BLOCKED)
+ task = self.loop.create_task(coro)
+
+ self.loop.call_soon(task.cancel)
+ try:
+ yield from task
+ except asyncio.CancelledError:
+ pass
+
+ # ignore the log:
+ # "Exception during subprocess creation, kill the subprocess"
+ with test_utils.disable_logger():
+ self.loop.run_until_complete(cancel_make_transport())
+
if sys.platform != 'win32':
# Unix