1import os 2import msvcrt 3import signal 4import sys 5import _winapi 6 7from .context import reduction, get_spawning_popen, set_spawning_popen 8from . import spawn 9from . import util 10 11__all__ = ['Popen'] 12 13# 14# 15# 16 17TERMINATE = 0x10000 18WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False)) 19WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") 20 21 22def _path_eq(p1, p2): 23 return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2) 24 25WINENV = not _path_eq(sys.executable, sys._base_executable) 26 27 28def _close_handles(*handles): 29 for handle in handles: 30 _winapi.CloseHandle(handle) 31 32 33# 34# We define a Popen class similar to the one from subprocess, but 35# whose constructor takes a process object as its argument. 36# 37 38class Popen(object): 39 ''' 40 Start a subprocess to run the code of a process object 41 ''' 42 method = 'spawn' 43 44 def __init__(self, process_obj): 45 prep_data = spawn.get_preparation_data(process_obj._name) 46 47 # read end of pipe will be duplicated by the child process 48 # -- see spawn_main() in spawn.py. 49 # 50 # bpo-33929: Previously, the read end of pipe was "stolen" by the child 51 # process, but it leaked a handle if the child process had been 52 # terminated before it could steal the handle from the parent process. 53 rhandle, whandle = _winapi.CreatePipe(None, 0) 54 wfd = msvcrt.open_osfhandle(whandle, 0) 55 cmd = spawn.get_command_line(parent_pid=os.getpid(), 56 pipe_handle=rhandle) 57 cmd = ' '.join('"%s"' % x for x in cmd) 58 59 python_exe = spawn.get_executable() 60 61 # bpo-35797: When running in a venv, we bypass the redirect 62 # executor and launch our base Python. 63 if WINENV and _path_eq(python_exe, sys.executable): 64 python_exe = sys._base_executable 65 env = os.environ.copy() 66 env["__PYVENV_LAUNCHER__"] = sys.executable 67 else: 68 env = None 69 70 with open(wfd, 'wb', closefd=True) as to_child: 71 # start process 72 try: 73 hp, ht, pid, tid = _winapi.CreateProcess( 74 python_exe, cmd, 75 None, None, False, 0, env, None, None) 76 _winapi.CloseHandle(ht) 77 except: 78 _winapi.CloseHandle(rhandle) 79 raise 80 81 # set attributes of self 82 self.pid = pid 83 self.returncode = None 84 self._handle = hp 85 self.sentinel = int(hp) 86 self.finalizer = util.Finalize(self, _close_handles, 87 (self.sentinel, int(rhandle))) 88 89 # send information to child 90 set_spawning_popen(self) 91 try: 92 reduction.dump(prep_data, to_child) 93 reduction.dump(process_obj, to_child) 94 finally: 95 set_spawning_popen(None) 96 97 def duplicate_for_child(self, handle): 98 assert self is get_spawning_popen() 99 return reduction.duplicate(handle, self.sentinel) 100 101 def wait(self, timeout=None): 102 if self.returncode is None: 103 if timeout is None: 104 msecs = _winapi.INFINITE 105 else: 106 msecs = max(0, int(timeout * 1000 + 0.5)) 107 108 res = _winapi.WaitForSingleObject(int(self._handle), msecs) 109 if res == _winapi.WAIT_OBJECT_0: 110 code = _winapi.GetExitCodeProcess(self._handle) 111 if code == TERMINATE: 112 code = -signal.SIGTERM 113 self.returncode = code 114 115 return self.returncode 116 117 def poll(self): 118 return self.wait(timeout=0) 119 120 def terminate(self): 121 if self.returncode is None: 122 try: 123 _winapi.TerminateProcess(int(self._handle), TERMINATE) 124 except OSError: 125 if self.wait(timeout=1.0) is None: 126 raise 127 128 kill = terminate 129 130 def close(self): 131 self.finalizer() 132