Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5969128

Browse files
author
Guido van Rossum
committed
asyncio: Add support for running subprocesses on Windows with the IOCP event loop (Richard Oudkerk).
1 parent 90fb914 commit 5969128

7 files changed

Lines changed: 137 additions & 195 deletions

File tree

Lib/asyncio/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44

55
# The selectors module is in the stdlib in Python 3.4 but not in 3.3.
66
# Do this first, so the other submodules can use "from . import selectors".
7+
# Prefer asyncio/selectors.py over the stdlib one, as ours may be newer.
78
try:
8-
import selectors # Will also be exported.
9-
except ImportError:
109
from . import selectors
10+
except ImportError:
11+
import selectors # Will also be exported.
12+
13+
if sys.platform == 'win32':
14+
# Similar thing for _overlapped.
15+
try:
16+
from . import _overlapped
17+
except ImportError:
18+
import _overlapped # Will also be exported.
1119

1220
# This relies on each of the submodules having an __all__ variable.
1321
from .futures import *

Lib/asyncio/proactor_events.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,15 @@ def _make_read_pipe_transport(self, sock, protocol, waiter=None,
267267
return _ProactorReadPipeTransport(self, sock, protocol, waiter, extra)
268268

269269
def _make_write_pipe_transport(self, sock, protocol, waiter=None,
270-
extra=None):
271-
return _ProactorWritePipeTransport(self, sock, protocol, waiter, extra)
270+
extra=None, check_for_hangup=True):
271+
if check_for_hangup:
272+
# We want connection_lost() to be called when other end closes
273+
return _ProactorDuplexPipeTransport(self,
274+
sock, protocol, waiter, extra)
275+
else:
276+
# If other end closes we may not notice for a long time
277+
return _ProactorWritePipeTransport(self, sock, protocol, waiter,
278+
extra)
272279

273280
def close(self):
274281
if self._proactor is not None:

Lib/asyncio/unix_events.py

Lines changed: 3 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Selector eventloop for Unix with signal handling."""
22

3-
import collections
43
import errno
54
import fcntl
65
import os
@@ -11,6 +10,7 @@
1110
import sys
1211

1312

13+
from . import base_subprocess
1414
from . import constants
1515
from . import events
1616
from . import protocols
@@ -406,159 +406,20 @@ def _call_connection_lost(self, exc):
406406
self._loop = None
407407

408408

409-
class _UnixWriteSubprocessPipeProto(protocols.BaseProtocol):
410-
pipe = None
409+
class _UnixSubprocessTransport(base_subprocess.BaseSubprocessTransport):
411410

412-
def __init__(self, proc, fd):
413-
self.proc = proc
414-
self.fd = fd
415-
self.connected = False
416-
self.disconnected = False
417-
proc._pipes[fd] = self
418-
419-
def connection_made(self, transport):
420-
self.connected = True
421-
self.pipe = transport
422-
self.proc._try_connected()
423-
424-
def connection_lost(self, exc):
425-
self.disconnected = True
426-
self.proc._pipe_connection_lost(self.fd, exc)
427-
428-
429-
class _UnixReadSubprocessPipeProto(_UnixWriteSubprocessPipeProto,
430-
protocols.Protocol):
431-
432-
def data_received(self, data):
433-
self.proc._pipe_data_received(self.fd, data)
434-
435-
def eof_received(self):
436-
pass
437-
438-
439-
class _UnixSubprocessTransport(transports.SubprocessTransport):
440-
441-
def __init__(self, loop, protocol, args, shell,
442-
stdin, stdout, stderr, bufsize,
443-
extra=None, **kwargs):
444-
super().__init__(extra)
445-
self._protocol = protocol
446-
self._loop = loop
447-
448-
self._pipes = {}
411+
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
449412
stdin_w = None
450413
if stdin == subprocess.PIPE:
451-
self._pipes[STDIN] = None
452414
# Use a socket pair for stdin, since not all platforms
453415
# support selecting read events on the write end of a
454416
# socket (which we use in order to detect closing of the
455417
# other end). Notably this is needed on AIX, and works
456418
# just fine on other platforms.
457419
stdin, stdin_w = self._loop._socketpair()
458-
if stdout == subprocess.PIPE:
459-
self._pipes[STDOUT] = None
460-
if stderr == subprocess.PIPE:
461-
self._pipes[STDERR] = None
462-
self._pending_calls = collections.deque()
463-
self._finished = False
464-
self._returncode = None
465-
466420
self._proc = subprocess.Popen(
467421
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
468422
universal_newlines=False, bufsize=bufsize, **kwargs)
469423
if stdin_w is not None:
470424
stdin.close()
471425
self._proc.stdin = open(stdin_w.detach(), 'rb', buffering=bufsize)
472-
self._extra['subprocess'] = self._proc
473-
474-
def close(self):
475-
for proto in self._pipes.values():
476-
proto.pipe.close()
477-
if self._returncode is None:
478-
self.terminate()
479-
480-
def get_pid(self):
481-
return self._proc.pid
482-
483-
def get_returncode(self):
484-
return self._returncode
485-
486-
def get_pipe_transport(self, fd):
487-
if fd in self._pipes:
488-
return self._pipes[fd].pipe
489-
else:
490-
return None
491-
492-
def send_signal(self, signal):
493-
self._proc.send_signal(signal)
494-
495-
def terminate(self):
496-
self._proc.terminate()
497-
498-
def kill(self):
499-
self._proc.kill()
500-
501-
@tasks.coroutine
502-
def _post_init(self):
503-
proc = self._proc
504-
loop = self._loop
505-
if proc.stdin is not None:
506-
transp, proto = yield from loop.connect_write_pipe(
507-
lambda: _UnixWriteSubprocessPipeProto(self, STDIN),
508-
proc.stdin)
509-
if proc.stdout is not None:
510-
transp, proto = yield from loop.connect_read_pipe(
511-
lambda: _UnixReadSubprocessPipeProto(self, STDOUT),
512-
proc.stdout)
513-
if proc.stderr is not None:
514-
transp, proto = yield from loop.connect_read_pipe(
515-
lambda: _UnixReadSubprocessPipeProto(self, STDERR),
516-
proc.stderr)
517-
if not self._pipes:
518-
self._try_connected()
519-
520-
def _call(self, cb, *data):
521-
if self._pending_calls is not None:
522-
self._pending_calls.append((cb, data))
523-
else:
524-
self._loop.call_soon(cb, *data)
525-
526-
def _try_connected(self):
527-
assert self._pending_calls is not None
528-
if all(p is not None and p.connected for p in self._pipes.values()):
529-
self._loop.call_soon(self._protocol.connection_made, self)
530-
for callback, data in self._pending_calls:
531-
self._loop.call_soon(callback, *data)
532-
self._pending_calls = None
533-
534-
def _pipe_connection_lost(self, fd, exc):
535-
self._call(self._protocol.pipe_connection_lost, fd, exc)
536-
self._try_finish()
537-
538-
def _pipe_data_received(self, fd, data):
539-
self._call(self._protocol.pipe_data_received, fd, data)
540-
541-
def _process_exited(self, returncode):
542-
assert returncode is not None, returncode
543-
assert self._returncode is None, self._returncode
544-
self._returncode = returncode
545-
self._loop._subprocess_closed(self)
546-
self._call(self._protocol.process_exited)
547-
self._try_finish()
548-
549-
def _try_finish(self):
550-
assert not self._finished
551-
if self._returncode is None:
552-
return
553-
if all(p is not None and p.disconnected
554-
for p in self._pipes.values()):
555-
self._finished = True
556-
self._loop.call_soon(self._call_connection_lost, None)
557-
558-
def _call_connection_lost(self, exc):
559-
try:
560-
self._protocol.connection_lost(exc)
561-
finally:
562-
self._proc = None
563-
self._protocol = None
564-
self._loop = None

Lib/asyncio/windows_events.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@
22

33
import errno
44
import socket
5+
import subprocess
56
import weakref
67
import struct
78
import _winapi
89

10+
from . import base_subprocess
911
from . import futures
1012
from . import proactor_events
1113
from . import selector_events
1214
from . import tasks
1315
from . import windows_utils
1416
from .log import logger
15-
16-
try:
17-
import _overlapped
18-
except ImportError:
19-
from . import _overlapped
17+
from . import _overlapped
2018

2119

2220
__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor']
@@ -168,6 +166,19 @@ def loop(f=None):
168166
def _stop_serving(self, server):
169167
server.close()
170168

169+
@tasks.coroutine
170+
def _make_subprocess_transport(self, protocol, args, shell,
171+
stdin, stdout, stderr, bufsize,
172+
extra=None, **kwargs):
173+
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
174+
stdin, stdout, stderr, bufsize,
175+
extra=None, **kwargs)
176+
yield from transp._post_init()
177+
return transp
178+
179+
def _subprocess_closed(self, transport):
180+
pass
181+
171182

172183
class IocpProactor:
173184
"""Proactor implementation using IOCP."""
@@ -413,3 +424,16 @@ def close(self):
413424
if self._iocp is not None:
414425
_winapi.CloseHandle(self._iocp)
415426
self._iocp = None
427+
428+
429+
class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
430+
431+
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
432+
self._proc = windows_utils.Popen(
433+
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
434+
bufsize=bufsize, **kwargs)
435+
def callback(f):
436+
returncode = self._proc.poll()
437+
self._process_exited(returncode)
438+
f = self._loop._proactor.wait_for_handle(int(self._proc._handle))
439+
f.add_done_callback(callback)

Lib/asyncio/windows_utils.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
BUFSIZE = 8192
2626
PIPE = subprocess.PIPE
27+
STDOUT = subprocess.STDOUT
2728
_mmap_counter = itertools.count()
2829

2930
#
@@ -146,24 +147,34 @@ class Popen(subprocess.Popen):
146147
The stdin, stdout, stderr are None or instances of PipeHandle.
147148
"""
148149
def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
150+
assert not kwds.get('universal_newlines')
151+
assert kwds.get('bufsize', 0) == 0
149152
stdin_rfd = stdout_wfd = stderr_wfd = None
150153
stdin_wh = stdout_rh = stderr_rh = None
151154
if stdin == PIPE:
152-
stdin_rh, stdin_wh = pipe(overlapped=(False, True))
155+
stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
153156
stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
157+
else:
158+
stdin_rfd = stdin
154159
if stdout == PIPE:
155160
stdout_rh, stdout_wh = pipe(overlapped=(True, False))
156161
stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
162+
else:
163+
stdout_wfd = stdout
157164
if stderr == PIPE:
158165
stderr_rh, stderr_wh = pipe(overlapped=(True, False))
159166
stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
167+
elif stderr == STDOUT:
168+
stderr_wfd = stdout_wfd
169+
else:
170+
stderr_wfd = stderr
160171
try:
161-
super().__init__(args, bufsize=0, universal_newlines=False,
162-
stdin=stdin_rfd, stdout=stdout_wfd,
172+
super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
163173
stderr=stderr_wfd, **kwds)
164174
except:
165175
for h in (stdin_wh, stdout_rh, stderr_rh):
166-
_winapi.CloseHandle(h)
176+
if h is not None:
177+
_winapi.CloseHandle(h)
167178
raise
168179
else:
169180
if stdin_wh is not None:

0 commit comments

Comments
 (0)