##// END OF EJS Templates
use new _winapi instead of removed _subprocess...
MinRK -
Show More
@@ -1,222 +1,226
1 1 """ Defines helper functions for creating kernel entry points and process
2 2 launchers.
3 3 """
4 4
5 5 # Standard library imports.
6 6 import atexit
7 7 import json
8 8 import os
9 9 import socket
10 10 from subprocess import Popen, PIPE
11 11 import sys
12 12 import tempfile
13 13
14 14 # System library imports
15 15
16 16 # IPython imports
17 17 from IPython.utils.localinterfaces import LOCALHOST
18 18 from IPython.utils.py3compat import bytes_to_str
19 19
20 20 # Local imports.
21 21 from parentpoller import ParentPollerWindows
22 22
23 23 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
24 24 ip=LOCALHOST, key=b'', transport='tcp'):
25 25 """Generates a JSON config file, including the selection of random ports.
26 26
27 27 Parameters
28 28 ----------
29 29
30 30 fname : unicode
31 31 The path to the file to write
32 32
33 33 shell_port : int, optional
34 34 The port to use for ROUTER channel.
35 35
36 36 iopub_port : int, optional
37 37 The port to use for the SUB channel.
38 38
39 39 stdin_port : int, optional
40 40 The port to use for the REQ (raw input) channel.
41 41
42 42 hb_port : int, optional
43 43 The port to use for the hearbeat REP channel.
44 44
45 45 ip : str, optional
46 46 The ip address the kernel will bind to.
47 47
48 48 key : str, optional
49 49 The Session key used for HMAC authentication.
50 50
51 51 """
52 52 # default to temporary connector file
53 53 if not fname:
54 54 fname = tempfile.mktemp('.json')
55 55
56 56 # Find open ports as necessary.
57 57
58 58 ports = []
59 59 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
60 60 int(stdin_port <= 0) + int(hb_port <= 0)
61 61 if transport == 'tcp':
62 62 for i in range(ports_needed):
63 63 sock = socket.socket()
64 64 sock.bind(('', 0))
65 65 ports.append(sock)
66 66 for i, sock in enumerate(ports):
67 67 port = sock.getsockname()[1]
68 68 sock.close()
69 69 ports[i] = port
70 70 else:
71 71 N = 1
72 72 for i in range(ports_needed):
73 73 while os.path.exists("%s-%s" % (ip, str(N))):
74 74 N += 1
75 75 ports.append(N)
76 76 N += 1
77 77 if shell_port <= 0:
78 78 shell_port = ports.pop(0)
79 79 if iopub_port <= 0:
80 80 iopub_port = ports.pop(0)
81 81 if stdin_port <= 0:
82 82 stdin_port = ports.pop(0)
83 83 if hb_port <= 0:
84 84 hb_port = ports.pop(0)
85 85
86 86 cfg = dict( shell_port=shell_port,
87 87 iopub_port=iopub_port,
88 88 stdin_port=stdin_port,
89 89 hb_port=hb_port,
90 90 )
91 91 cfg['ip'] = ip
92 92 cfg['key'] = bytes_to_str(key)
93 93 cfg['transport'] = transport
94 94
95 95 with open(fname, 'w') as f:
96 96 f.write(json.dumps(cfg, indent=2))
97 97
98 98 return fname, cfg
99 99
100 100
101 101 def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None,
102 102 executable=None, independent=False, extra_arguments=[],
103 103 cwd=None):
104 104 """ Launches a localhost kernel, binding to the specified ports.
105 105
106 106 Parameters
107 107 ----------
108 108 code : str,
109 109 A string of Python code that imports and executes a kernel entry point.
110 110
111 111 stdin, stdout, stderr : optional (default None)
112 112 Standards streams, as defined in subprocess.Popen.
113 113
114 114 fname : unicode, optional
115 115 The JSON connector file, containing ip/port/hmac key information.
116 116
117 117 key : str, optional
118 118 The Session key used for HMAC authentication.
119 119
120 120 executable : str, optional (default sys.executable)
121 121 The Python executable to use for the kernel process.
122 122
123 123 independent : bool, optional (default False)
124 124 If set, the kernel process is guaranteed to survive if this process
125 125 dies. If not set, an effort is made to ensure that the kernel is killed
126 126 when this process dies. Note that in this case it is still good practice
127 127 to kill kernels manually before exiting.
128 128
129 129 extra_arguments : list, optional
130 130 A list of extra arguments to pass when executing the launch code.
131 131
132 132 cwd : path, optional
133 133 The working dir of the kernel process (default: cwd of this process).
134 134
135 135 Returns
136 136 -------
137 137 A tuple of form:
138 138 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
139 139 where kernel_process is a Popen object and the ports are integers.
140 140 """
141 141
142 142 # Build the kernel launch command.
143 143 if executable is None:
144 144 executable = sys.executable
145 145 arguments = [ executable, '-c', code, '-f', fname ]
146 146 arguments.extend(extra_arguments)
147 147
148 148 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
149 149 # are invalid. Unfortunately, there is in general no way to detect whether
150 150 # they are valid. The following two blocks redirect them to (temporary)
151 151 # pipes in certain important cases.
152 152
153 153 # If this process has been backgrounded, our stdin is invalid. Since there
154 154 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
155 155 # place this one safe and always redirect.
156 156 redirect_in = True
157 157 _stdin = PIPE if stdin is None else stdin
158 158
159 159 # If this process in running on pythonw, we know that stdin, stdout, and
160 160 # stderr are all invalid.
161 161 redirect_out = sys.executable.endswith('pythonw.exe')
162 162 if redirect_out:
163 163 _stdout = PIPE if stdout is None else stdout
164 164 _stderr = PIPE if stderr is None else stderr
165 165 else:
166 166 _stdout, _stderr = stdout, stderr
167 167
168 168 # Spawn a kernel.
169 169 if sys.platform == 'win32':
170 170 # Create a Win32 event for interrupting the kernel.
171 171 interrupt_event = ParentPollerWindows.create_interrupt_event()
172 172 arguments += [ '--interrupt=%i'%interrupt_event ]
173 173
174 174 # If the kernel is running on pythonw and stdout/stderr are not been
175 175 # re-directed, it will crash when more than 4KB of data is written to
176 176 # stdout or stderr. This is a bug that has been with Python for a very
177 177 # long time; see http://bugs.python.org/issue706263.
178 178 # A cleaner solution to this problem would be to pass os.devnull to
179 179 # Popen directly. Unfortunately, that does not work.
180 180 if executable.endswith('pythonw.exe'):
181 181 if stdout is None:
182 182 arguments.append('--no-stdout')
183 183 if stderr is None:
184 184 arguments.append('--no-stderr')
185 185
186 186 # Launch the kernel process.
187 187 if independent:
188 188 proc = Popen(arguments,
189 189 creationflags=512, # CREATE_NEW_PROCESS_GROUP
190 190 stdin=_stdin, stdout=_stdout, stderr=_stderr)
191 191 else:
192 from _subprocess import DuplicateHandle, GetCurrentProcess, \
193 DUPLICATE_SAME_ACCESS
192 try:
193 from _winapi import DuplicateHandle, GetCurrentProcess, \
194 DUPLICATE_SAME_ACCESS
195 except:
196 from _subprocess import DuplicateHandle, GetCurrentProcess, \
197 DUPLICATE_SAME_ACCESS
194 198 pid = GetCurrentProcess()
195 199 handle = DuplicateHandle(pid, pid, pid, 0,
196 200 True, # Inheritable by new processes.
197 201 DUPLICATE_SAME_ACCESS)
198 202 proc = Popen(arguments + ['--parent=%i'%int(handle)],
199 203 stdin=_stdin, stdout=_stdout, stderr=_stderr)
200 204
201 205 # Attach the interrupt event to the Popen objet so it can be used later.
202 206 proc.win32_interrupt_event = interrupt_event
203 207
204 208 else:
205 209 if independent:
206 210 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
207 211 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
208 212 else:
209 213 proc = Popen(arguments + ['--parent=1'],
210 214 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
211 215
212 216 # Clean up pipes created to work around Popen bug.
213 217 if redirect_in:
214 218 if stdin is None:
215 219 proc.stdin.close()
216 220 if redirect_out:
217 221 if stdout is None:
218 222 proc.stdout.close()
219 223 if stderr is None:
220 224 proc.stderr.close()
221 225
222 226 return proc
@@ -1,136 +1,139
1 1 # Standard library imports.
2 2 try:
3 3 import ctypes
4 4 except:
5 5 ctypes = None
6 6 import os
7 7 import platform
8 8 import time
9 9 from thread import interrupt_main
10 10 from threading import Thread
11 11
12 12 from IPython.utils.warn import warn
13 13
14 14
15 15 class ParentPollerUnix(Thread):
16 16 """ A Unix-specific daemon thread that terminates the program immediately
17 17 when the parent process no longer exists.
18 18 """
19 19
20 20 def __init__(self):
21 21 super(ParentPollerUnix, self).__init__()
22 22 self.daemon = True
23 23
24 24 def run(self):
25 25 # We cannot use os.waitpid because it works only for child processes.
26 26 from errno import EINTR
27 27 while True:
28 28 try:
29 29 if os.getppid() == 1:
30 30 os._exit(1)
31 31 time.sleep(1.0)
32 32 except OSError as e:
33 33 if e.errno == EINTR:
34 34 continue
35 35 raise
36 36
37 37
38 38 class ParentPollerWindows(Thread):
39 39 """ A Windows-specific daemon thread that listens for a special event that
40 40 signals an interrupt and, optionally, terminates the program immediately
41 41 when the parent process no longer exists.
42 42 """
43 43
44 44 def __init__(self, interrupt_handle=None, parent_handle=None):
45 45 """ Create the poller. At least one of the optional parameters must be
46 46 provided.
47 47
48 48 Parameters:
49 49 -----------
50 50 interrupt_handle : HANDLE (int), optional
51 51 If provided, the program will generate a Ctrl+C event when this
52 52 handle is signaled.
53 53
54 54 parent_handle : HANDLE (int), optional
55 55 If provided, the program will terminate immediately when this
56 56 handle is signaled.
57 57 """
58 58 assert(interrupt_handle or parent_handle)
59 59 super(ParentPollerWindows, self).__init__()
60 60 if ctypes is None:
61 61 raise ImportError("ParentPollerWindows requires ctypes")
62 62 self.daemon = True
63 63 self.interrupt_handle = interrupt_handle
64 64 self.parent_handle = parent_handle
65 65
66 66 @staticmethod
67 67 def create_interrupt_event():
68 68 """ Create an interrupt event handle.
69 69
70 70 The parent process should use this static method for creating the
71 71 interrupt event that is passed to the child process. It should store
72 72 this handle and use it with ``send_interrupt`` to interrupt the child
73 73 process.
74 74 """
75 75 # Create a security attributes struct that permits inheritance of the
76 76 # handle by new processes.
77 77 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
78 78 class SECURITY_ATTRIBUTES(ctypes.Structure):
79 79 _fields_ = [ ("nLength", ctypes.c_int),
80 80 ("lpSecurityDescriptor", ctypes.c_void_p),
81 81 ("bInheritHandle", ctypes.c_int) ]
82 82 sa = SECURITY_ATTRIBUTES()
83 83 sa_p = ctypes.pointer(sa)
84 84 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
85 85 sa.lpSecurityDescriptor = 0
86 86 sa.bInheritHandle = 1
87 87
88 88 return ctypes.windll.kernel32.CreateEventA(
89 89 sa_p, # lpEventAttributes
90 90 False, # bManualReset
91 91 False, # bInitialState
92 92 '') # lpName
93 93
94 94 @staticmethod
95 95 def send_interrupt(interrupt_handle):
96 96 """ Sends an interrupt event using the specified handle.
97 97 """
98 98 ctypes.windll.kernel32.SetEvent(interrupt_handle)
99 99
100 100 def run(self):
101 101 """ Run the poll loop. This method never returns.
102 102 """
103 from _subprocess import WAIT_OBJECT_0, INFINITE
103 try:
104 from _winapi import WAIT_OBJECT_0, INFINITE
105 except ImportError:
106 from _subprocess import WAIT_OBJECT_0, INFINITE
104 107
105 108 # Build the list of handle to listen on.
106 109 handles = []
107 110 if self.interrupt_handle:
108 111 handles.append(self.interrupt_handle)
109 112 if self.parent_handle:
110 113 handles.append(self.parent_handle)
111 114 arch = platform.architecture()[0]
112 115 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
113 116
114 117 # Listen forever.
115 118 while True:
116 119 result = ctypes.windll.kernel32.WaitForMultipleObjects(
117 120 len(handles), # nCount
118 121 (c_int * len(handles))(*handles), # lpHandles
119 122 False, # bWaitAll
120 123 INFINITE) # dwMilliseconds
121 124
122 125 if WAIT_OBJECT_0 <= result < len(handles):
123 126 handle = handles[result - WAIT_OBJECT_0]
124 127
125 128 if handle == self.interrupt_handle:
126 129 interrupt_main()
127 130
128 131 elif handle == self.parent_handle:
129 132 os._exit(1)
130 133 elif result < 0:
131 134 # wait failed, just give up and stop polling.
132 135 warn("""Parent poll failed. If the frontend dies,
133 136 the kernel may be left running. Please let us know
134 137 about your system (bitness, Python, etc.) at
135 138 ipython-dev@scipy.org""")
136 139 return
General Comments 0
You need to be logged in to leave comments. Login now