diff -r a027605696a3 asyncio/base_subprocess.py --- a/asyncio/base_subprocess.py Tue Feb 17 23:34:16 2015 +0100 +++ b/asyncio/base_subprocess.py Fri Feb 27 19:10:23 2015 +0100 @@ -111,6 +111,24 @@ # Don't clear the _proc reference yet: _post_init() may still run + def detach(self): + """Put the transport in terminated state without actually terminating + the process. The process object is returned and can be reused. + + It can be useful to close the transport without terminating the + process when we want to work the child process outside the loop. + + transport.close() can be called to close the pipe transports without + + Process stdin and stdout are left in non-blocking mode. """ + for fd, proto in self._pipes.items(): + if proto: + proto.pipe.detach() + self._pipes[fd] = None + + self._proc = None + return self._extra.pop('subprocess') + # On Python 3.3 and older, objects with a destructor part of a reference # cycle are never destroyed. It's not more the case on Python 3.4 thanks # to the PEP 442. diff -r a027605696a3 asyncio/proactor_events.py --- a/asyncio/proactor_events.py Tue Feb 17 23:34:16 2015 +0100 +++ b/asyncio/proactor_events.py Fri Feb 27 19:10:23 2015 +0100 @@ -43,6 +43,12 @@ # only wake up the waiter when connection_made() has been called self._loop.call_soon(waiter._set_result_unless_cancelled, None) + def detach(self): + self.close() + sock = self._sock.fileno() + self._sock = None + return sock + def __repr__(self): info = [self.__class__.__name__] if self._sock is None: diff -r a027605696a3 asyncio/unix_events.py --- a/asyncio/unix_events.py Tue Feb 17 23:34:16 2015 +0100 +++ b/asyncio/unix_events.py Fri Feb 27 19:10:23 2015 +0100 @@ -367,6 +367,12 @@ if not self._closing: self._close(None) + def detach(self): + """Detach the pipe from the transport and return its fd.""" + self._loop.remove_reader(self._fileno) + self._pipe = None + return self._fileno + # On Python 3.3 and older, objects with a destructor part of a reference # cycle are never destroyed. It's not more the case on Python 3.4 thanks # to the PEP 442. @@ -595,6 +601,15 @@ self._protocol = None self._loop = None + def detach(self): + """Detach the pipe from the transport and return its fd.""" + if self._buffer: + self._loop.remove_writer(self._fileno) + self._buffer.clear() + self._loop.remove_reader(self._fileno) + self._pipe = None + return self._fileno + if hasattr(os, 'set_inheritable'): # Python 3.4 and newer @@ -636,6 +651,12 @@ stdin.close() self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize) + def detach(self): + proc = super().detach() + if proc is not None: + events.get_child_watcher().remove_child_handler(proc.pid) + return proc + class AbstractChildWatcher: """Abstract base class for monitoring child processes. diff -r a027605696a3 tests/test_subprocess.py --- a/tests/test_subprocess.py Tue Feb 17 23:34:16 2015 +0100 +++ b/tests/test_subprocess.py Fri Feb 27 19:10:23 2015 +0100 @@ -73,6 +73,41 @@ transport.close() + def test_detach_process_without_pipes(self): + waiter = asyncio.Future(loop=self.loop) + transport, protocol = self.create_transport(waiter) + self.loop.run_until_complete(waiter) + + transport.detach() + + self.assertIsNone(transport._proc) + self.assertIsNone(transport.get_extra_info('subprocess')) + + self.assertRaises(ProcessLookupError, + transport.send_signal, signal.SIGTERM) + self.assertRaises(ProcessLookupError, transport.terminate) + self.assertRaises(ProcessLookupError, transport.kill) + + def test_detach_process_with_pipes(self): + waiter = asyncio.Future(loop=self.loop) + transport, protocol = self.create_transport(waiter) + self.loop.run_until_complete(waiter) + + class MockPipeTransport: + def detach(self): + pass + + for fd in transport._pipes: + transport._pipes[fd] = MockPipeTransport() + + pipes = transport._pipes.copy() + + transport.detach() + + for fd in pipes: + self.assertIsNone(pipes[fd]._pipe) + self.assertIsNone(transport._pipes[fd]) + class SubprocessMixin: