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