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