##// END OF EJS Templates
split base_launch_kernel into make_kernel_cmd / launch_kernel
MinRK -
Show More
@@ -1,226 +1,262 b''
1 1 """ Defines helper functions for creating kernel entry points and process
2 2 launchers.
3 3 """
4 4
5 # Standard library imports.
6 import atexit
5 # Standard library imports
7 6 import json
8 7 import os
9 8 import socket
10 9 from subprocess import Popen, PIPE
11 10 import sys
12 11 import tempfile
13 12
14 13 # System library imports
15 14
16 15 # IPython imports
17 16 from IPython.utils.localinterfaces import LOCALHOST
18 17 from IPython.utils.py3compat import bytes_to_str
19 18
20 # Local imports.
19 # Local imports
21 20 from parentpoller import ParentPollerWindows
22 21
23 22 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
24 23 ip=LOCALHOST, key=b'', transport='tcp'):
25 24 """Generates a JSON config file, including the selection of random ports.
26 25
27 26 Parameters
28 27 ----------
29 28
30 29 fname : unicode
31 30 The path to the file to write
32 31
33 32 shell_port : int, optional
34 33 The port to use for ROUTER channel.
35 34
36 35 iopub_port : int, optional
37 36 The port to use for the SUB channel.
38 37
39 38 stdin_port : int, optional
40 39 The port to use for the REQ (raw input) channel.
41 40
42 41 hb_port : int, optional
43 42 The port to use for the hearbeat REP channel.
44 43
45 44 ip : str, optional
46 45 The ip address the kernel will bind to.
47 46
48 47 key : str, optional
49 48 The Session key used for HMAC authentication.
50 49
51 50 """
52 51 # default to temporary connector file
53 52 if not fname:
54 53 fname = tempfile.mktemp('.json')
55 54
56 55 # Find open ports as necessary.
57 56
58 57 ports = []
59 58 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
60 59 int(stdin_port <= 0) + int(hb_port <= 0)
61 60 if transport == 'tcp':
62 61 for i in range(ports_needed):
63 62 sock = socket.socket()
64 63 sock.bind(('', 0))
65 64 ports.append(sock)
66 65 for i, sock in enumerate(ports):
67 66 port = sock.getsockname()[1]
68 67 sock.close()
69 68 ports[i] = port
70 69 else:
71 70 N = 1
72 71 for i in range(ports_needed):
73 72 while os.path.exists("%s-%s" % (ip, str(N))):
74 73 N += 1
75 74 ports.append(N)
76 75 N += 1
77 76 if shell_port <= 0:
78 77 shell_port = ports.pop(0)
79 78 if iopub_port <= 0:
80 79 iopub_port = ports.pop(0)
81 80 if stdin_port <= 0:
82 81 stdin_port = ports.pop(0)
83 82 if hb_port <= 0:
84 83 hb_port = ports.pop(0)
85 84
86 85 cfg = dict( shell_port=shell_port,
87 86 iopub_port=iopub_port,
88 87 stdin_port=stdin_port,
89 88 hb_port=hb_port,
90 89 )
91 90 cfg['ip'] = ip
92 91 cfg['key'] = bytes_to_str(key)
93 92 cfg['transport'] = transport
94 93
95 94 with open(fname, 'w') as f:
96 95 f.write(json.dumps(cfg, indent=2))
97 96
98 97 return fname, cfg
99 98
100 99
101 def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None,
102 executable=None, independent=False, extra_arguments=[],
103 cwd=None):
100 def make_kernel_cmd(code, executable=None, extra_arguments=[], **kw):
104 101 """ Launches a localhost kernel, binding to the specified ports.
105 102
106 103 Parameters
107 104 ----------
108 105 code : str,
109 106 A string of Python code that imports and executes a kernel entry point.
110 107
111 stdin, stdout, stderr : optional (default None)
112 Standards streams, as defined in subprocess.Popen.
108 executable : str, optional (default sys.executable)
109 The Python executable to use for the kernel process.
110
111 extra_arguments : list, optional
112 A list of extra arguments to pass when executing the launch code.
113
114 Returns
115 -------
116
117 A Popen command list
118 """
119
120 # Build the kernel launch command.
121 if executable is None:
122 executable = sys.executable
123 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
124 arguments.extend(extra_arguments)
113 125
114 fname : unicode, optional
115 The JSON connector file, containing ip/port/hmac key information.
126 # Spawn a kernel.
127 if sys.platform == 'win32':
116 128
117 key : str, optional
118 The Session key used for HMAC authentication.
129 # If the kernel is running on pythonw and stdout/stderr are not been
130 # re-directed, it will crash when more than 4KB of data is written to
131 # stdout or stderr. This is a bug that has been with Python for a very
132 # long time; see http://bugs.python.org/issue706263.
133 # A cleaner solution to this problem would be to pass os.devnull to
134 # Popen directly. Unfortunately, that does not work.
135 if executable.endswith('pythonw.exe'):
136 arguments.append('--no-stdout')
137 arguments.append('--no-stderr')
119 138
120 executable : str, optional (default sys.executable)
121 The Python executable to use for the kernel process.
139 return arguments
140
141
142 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
143 independent=False,
144 cwd=None, ipython_kernel=True,
145 **kw
146 ):
147 """ Launches a localhost kernel, binding to the specified ports.
148
149 Parameters
150 ----------
151 cmd : Popen list,
152 A string of Python code that imports and executes a kernel entry point.
153
154 stdin, stdout, stderr : optional (default None)
155 Standards streams, as defined in subprocess.Popen.
122 156
123 157 independent : bool, optional (default False)
124 158 If set, the kernel process is guaranteed to survive if this process
125 159 dies. If not set, an effort is made to ensure that the kernel is killed
126 160 when this process dies. Note that in this case it is still good practice
127 161 to kill kernels manually before exiting.
128 162
129 extra_arguments : list, optional
130 A list of extra arguments to pass when executing the launch code.
131
132 163 cwd : path, optional
133 164 The working dir of the kernel process (default: cwd of this process).
134 165
166 ipython_kernel : bool, optional
167 Whether the kernel is an official IPython one,
168 and should get a bit of special treatment.
169
135 170 Returns
136 171 -------
137 A tuple of form:
138 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
139 where kernel_process is a Popen object and the ports are integers.
140 """
141 172
142 # Build the kernel launch command.
143 if executable is None:
144 executable = sys.executable
145 arguments = [ executable, '-c', code, '-f', fname ]
146 arguments.extend(extra_arguments)
173 Popen instance for the kernel subprocess
174 """
147 175
148 176 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
149 177 # are invalid. Unfortunately, there is in general no way to detect whether
150 178 # they are valid. The following two blocks redirect them to (temporary)
151 179 # pipes in certain important cases.
152 180
153 181 # If this process has been backgrounded, our stdin is invalid. Since there
154 182 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
155 183 # place this one safe and always redirect.
156 184 redirect_in = True
157 185 _stdin = PIPE if stdin is None else stdin
158 186
159 187 # If this process in running on pythonw, we know that stdin, stdout, and
160 188 # stderr are all invalid.
161 189 redirect_out = sys.executable.endswith('pythonw.exe')
162 190 if redirect_out:
163 191 _stdout = PIPE if stdout is None else stdout
164 192 _stderr = PIPE if stderr is None else stderr
165 193 else:
166 194 _stdout, _stderr = stdout, stderr
167 195
168 196 # Spawn a kernel.
169 197 if sys.platform == 'win32':
198
170 199 # Create a Win32 event for interrupting the kernel.
171 200 interrupt_event = ParentPollerWindows.create_interrupt_event()
172 arguments += [ '--interrupt=%i'%interrupt_event ]
173
174 # If the kernel is running on pythonw and stdout/stderr are not been
175 # re-directed, it will crash when more than 4KB of data is written to
176 # stdout or stderr. This is a bug that has been with Python for a very
177 # long time; see http://bugs.python.org/issue706263.
178 # A cleaner solution to this problem would be to pass os.devnull to
179 # Popen directly. Unfortunately, that does not work.
180 if executable.endswith('pythonw.exe'):
181 if stdout is None:
182 arguments.append('--no-stdout')
183 if stderr is None:
184 arguments.append('--no-stderr')
201 if ipython_kernel:
202 cmd += [ '--interrupt=%i' % interrupt_event ]
203
204 # If the kernel is running on pythonw and stdout/stderr are not been
205 # re-directed, it will crash when more than 4KB of data is written to
206 # stdout or stderr. This is a bug that has been with Python for a very
207 # long time; see http://bugs.python.org/issue706263.
208 # A cleaner solution to this problem would be to pass os.devnull to
209 # Popen directly. Unfortunately, that does not work.
210 if cmd[0].endswith('pythonw.exe'):
211 if stdout is None:
212 cmd.append('--no-stdout')
213 if stderr is None:
214 cmd.append('--no-stderr')
185 215
186 216 # Launch the kernel process.
187 217 if independent:
188 proc = Popen(arguments,
218 proc = Popen(cmd,
189 219 creationflags=512, # CREATE_NEW_PROCESS_GROUP
190 220 stdin=_stdin, stdout=_stdout, stderr=_stderr)
191 221 else:
192 try:
193 from _winapi import DuplicateHandle, GetCurrentProcess, \
194 DUPLICATE_SAME_ACCESS
195 except:
196 from _subprocess import DuplicateHandle, GetCurrentProcess, \
197 DUPLICATE_SAME_ACCESS
198 pid = GetCurrentProcess()
199 handle = DuplicateHandle(pid, pid, pid, 0,
200 True, # Inheritable by new processes.
201 DUPLICATE_SAME_ACCESS)
202 proc = Popen(arguments + ['--parent=%i'%int(handle)],
203 stdin=_stdin, stdout=_stdout, stderr=_stderr)
222 if ipython_kernel:
223 try:
224 from _winapi import DuplicateHandle, GetCurrentProcess, \
225 DUPLICATE_SAME_ACCESS
226 except:
227 from _subprocess import DuplicateHandle, GetCurrentProcess, \
228 DUPLICATE_SAME_ACCESS
229 pid = GetCurrentProcess()
230 handle = DuplicateHandle(pid, pid, pid, 0,
231 True, # Inheritable by new processes.
232 DUPLICATE_SAME_ACCESS)
233 cmd +=[ '--parent=%i' % handle ]
234
235
236 proc = Popen(cmd,
237 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
204 238
205 239 # Attach the interrupt event to the Popen objet so it can be used later.
206 240 proc.win32_interrupt_event = interrupt_event
207 241
208 242 else:
209 243 if independent:
210 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
244 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
211 245 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
212 246 else:
213 proc = Popen(arguments + ['--parent=1'],
247 if ipython_kernel:
248 cmd += ['--parent=1']
249 proc = Popen(cmd,
214 250 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
215 251
216 252 # Clean up pipes created to work around Popen bug.
217 253 if redirect_in:
218 254 if stdin is None:
219 255 proc.stdin.close()
220 256 if redirect_out:
221 257 if stdout is None:
222 258 proc.stdout.close()
223 259 if stderr is None:
224 260 proc.stderr.close()
225 261
226 262 return proc
General Comments 0
You need to be logged in to leave comments. Login now