[Python-checkins] cpython (3.4): Python issue #23173: sync with Tulip

victor.stinner python-checkins at python.org
Wed Jan 14 02:15:03 CET 2015


https://hg.python.org/cpython/rev/1eae3b6fbec6
changeset:   94133:1eae3b6fbec6
branch:      3.4
parent:      94131:94a6f9a3580e
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Wed Jan 14 02:10:33 2015 +0100
summary:
  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.

files:
  Lib/asyncio/base_subprocess.py           |  73 ++++++++---
  Lib/asyncio/subprocess.py                |  16 ++-
  Lib/test/test_asyncio/test_subprocess.py |  36 +++++
  3 files changed, 100 insertions(+), 25 deletions(-)


diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -96,32 +96,61 @@
     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
+        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
+            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
+            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
--- a/Lib/asyncio/subprocess.py
+++ b/Lib/asyncio/subprocess.py
@@ -60,7 +60,9 @@
                                               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 @@
                                             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 @@
                                             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
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -251,6 +251,42 @@
 
         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

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list