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 58 python_exe = spawn.get_executable() 59 60 # bpo-35797: When running in a venv, we bypass the redirect 61 # executor and launch our base Python. 62 if WINENV and _path_eq(python_exe, sys.executable): 63 cmd[0] = python_exe = sys._base_executable 64 env = os.environ.copy() 65 env["__PYVENV_LAUNCHER__"] = sys.executable 66 else: 67 env = None 68 69 cmd = ' '.join('"%s"' % x for x in cmd) 70 71 with open(wfd, 'wb', closefd=True) as to_child: 72 # start process 73 try: 74 hp, ht, pid, tid = _winapi.CreateProcess( 75 python_exe, cmd, 76 None, None, False, 0, env, None, None) 77 _winapi.CloseHandle(ht) 78 except: 79 _winapi.CloseHandle(rhandle) 80 raise 81 82 # set attributes of self 83 self.pid = pid 84 self.returncode = None 85 self._handle = hp 86 self.sentinel = int(hp) 87 self.finalizer = util.Finalize(self, _close_handles, 88 (self.sentinel, int(rhandle))) 89 90 # send information to child 91 set_spawning_popen(self) 92 try: 93 reduction.dump(prep_data, to_child) 94 reduction.dump(process_obj, to_child) 95 finally: 96 set_spawning_popen(None) 97 98 def duplicate_for_child(self, handle): 99 assert self is get_spawning_popen() 100 return reduction.duplicate(handle, self.sentinel) 101 102 def wait(self, timeout=None): 103 if self.returncode is None: 104 if timeout is None: 105 msecs = _winapi.INFINITE 106 else: 107 msecs = max(0, int(timeout * 1000 + 0.5)) 108 109 res = _winapi.WaitForSingleObject(int(self._handle), msecs) 110 if res == _winapi.WAIT_OBJECT_0: 111 code = _winapi.GetExitCodeProcess(self._handle) 112 if code == TERMINATE: 113 code = -signal.SIGTERM 114 self.returncode = code 115 116 return self.returncode 117 118 def poll(self): 119 return self.wait(timeout=0) 120 121 def terminate(self): 122 if self.returncode is None: 123 try: 124 _winapi.TerminateProcess(int(self._handle), TERMINATE) 125 except OSError: 126 if self.wait(timeout=1.0) is None: 127 raise 128 129 kill = terminate 130 131 def close(self): 132 self.finalizer() 133