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