##// END OF EJS Templates
move IPython.zmq.entry_point to IPython.utils.kernel
MinRK -
Show More
@@ -1,315 +1,569 b''
1 """Utilities for connecting to kernels
1 """Utilities for working with kernels and their connection files
2
2
3 Authors:
3 Authors:
4
4
5 * Min Ragan-Kelley
5 * Min Ragan-Kelley
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import glob
20 import glob
21 import json
21 import json
22 import os
22 import os
23 import socket
23 import sys
24 import sys
24 from getpass import getpass
25 from getpass import getpass
25 from subprocess import Popen, PIPE
26 from subprocess import Popen, PIPE
27 import tempfile
26
28
27 # external imports
29 # external imports
28 from IPython.external.ssh import tunnel
30 from IPython.external.ssh import tunnel
29
31
30 # IPython imports
32 # IPython imports
31 from IPython.core.profiledir import ProfileDir
33 from IPython.core.profiledir import ProfileDir
34 from IPython.utils.localinterfaces import LOCALHOST
32 from IPython.utils.path import filefind, get_ipython_dir
35 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.py3compat import str_to_bytes
36 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
34
37
35
38
36 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
37 # Functions
40 # Working with Connection Files
38 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
39
42
43 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
44 ip=LOCALHOST, key=b'', transport='tcp'):
45 """Generates a JSON config file, including the selection of random ports.
46
47 Parameters
48 ----------
49
50 fname : unicode
51 The path to the file to write
52
53 shell_port : int, optional
54 The port to use for ROUTER channel.
55
56 iopub_port : int, optional
57 The port to use for the SUB channel.
58
59 stdin_port : int, optional
60 The port to use for the REQ (raw input) channel.
61
62 hb_port : int, optional
63 The port to use for the hearbeat REP channel.
64
65 ip : str, optional
66 The ip address the kernel will bind to.
67
68 key : str, optional
69 The Session key used for HMAC authentication.
70
71 """
72 # default to temporary connector file
73 if not fname:
74 fname = tempfile.mktemp('.json')
75
76 # Find open ports as necessary.
77
78 ports = []
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
80 int(stdin_port <= 0) + int(hb_port <= 0)
81 if transport == 'tcp':
82 for i in range(ports_needed):
83 sock = socket.socket()
84 sock.bind(('', 0))
85 ports.append(sock)
86 for i, sock in enumerate(ports):
87 port = sock.getsockname()[1]
88 sock.close()
89 ports[i] = port
90 else:
91 N = 1
92 for i in range(ports_needed):
93 while os.path.exists("%s-%s" % (ip, str(N))):
94 N += 1
95 ports.append(N)
96 N += 1
97 if shell_port <= 0:
98 shell_port = ports.pop(0)
99 if iopub_port <= 0:
100 iopub_port = ports.pop(0)
101 if stdin_port <= 0:
102 stdin_port = ports.pop(0)
103 if hb_port <= 0:
104 hb_port = ports.pop(0)
105
106 cfg = dict( shell_port=shell_port,
107 iopub_port=iopub_port,
108 stdin_port=stdin_port,
109 hb_port=hb_port,
110 )
111 cfg['ip'] = ip
112 cfg['key'] = bytes_to_str(key)
113 cfg['transport'] = transport
114
115 with open(fname, 'w') as f:
116 f.write(json.dumps(cfg, indent=2))
117
118 return fname, cfg
119
120
40 def get_connection_file(app=None):
121 def get_connection_file(app=None):
41 """Return the path to the connection file of an app
122 """Return the path to the connection file of an app
42
123
43 Parameters
124 Parameters
44 ----------
125 ----------
45 app : KernelApp instance [optional]
126 app : KernelApp instance [optional]
46 If unspecified, the currently running app will be used
127 If unspecified, the currently running app will be used
47 """
128 """
48 if app is None:
129 if app is None:
49 from IPython.zmq.ipkernel import IPKernelApp
130 from IPython.zmq.ipkernel import IPKernelApp
50 if not IPKernelApp.initialized():
131 if not IPKernelApp.initialized():
51 raise RuntimeError("app not specified, and not in a running Kernel")
132 raise RuntimeError("app not specified, and not in a running Kernel")
52
133
53 app = IPKernelApp.instance()
134 app = IPKernelApp.instance()
54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
135 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
55
136
137
56 def find_connection_file(filename, profile=None):
138 def find_connection_file(filename, profile=None):
57 """find a connection file, and return its absolute path.
139 """find a connection file, and return its absolute path.
58
140
59 The current working directory and the profile's security
141 The current working directory and the profile's security
60 directory will be searched for the file if it is not given by
142 directory will be searched for the file if it is not given by
61 absolute path.
143 absolute path.
62
144
63 If profile is unspecified, then the current running application's
145 If profile is unspecified, then the current running application's
64 profile will be used, or 'default', if not run from IPython.
146 profile will be used, or 'default', if not run from IPython.
65
147
66 If the argument does not match an existing file, it will be interpreted as a
148 If the argument does not match an existing file, it will be interpreted as a
67 fileglob, and the matching file in the profile's security dir with
149 fileglob, and the matching file in the profile's security dir with
68 the latest access time will be used.
150 the latest access time will be used.
69
151
70 Parameters
152 Parameters
71 ----------
153 ----------
72 filename : str
154 filename : str
73 The connection file or fileglob to search for.
155 The connection file or fileglob to search for.
74 profile : str [optional]
156 profile : str [optional]
75 The name of the profile to use when searching for the connection file,
157 The name of the profile to use when searching for the connection file,
76 if different from the current IPython session or 'default'.
158 if different from the current IPython session or 'default'.
77
159
78 Returns
160 Returns
79 -------
161 -------
80 str : The absolute path of the connection file.
162 str : The absolute path of the connection file.
81 """
163 """
82 from IPython.core.application import BaseIPythonApplication as IPApp
164 from IPython.core.application import BaseIPythonApplication as IPApp
83 try:
165 try:
84 # quick check for absolute path, before going through logic
166 # quick check for absolute path, before going through logic
85 return filefind(filename)
167 return filefind(filename)
86 except IOError:
168 except IOError:
87 pass
169 pass
88
170
89 if profile is None:
171 if profile is None:
90 # profile unspecified, check if running from an IPython app
172 # profile unspecified, check if running from an IPython app
91 if IPApp.initialized():
173 if IPApp.initialized():
92 app = IPApp.instance()
174 app = IPApp.instance()
93 profile_dir = app.profile_dir
175 profile_dir = app.profile_dir
94 else:
176 else:
95 # not running in IPython, use default profile
177 # not running in IPython, use default profile
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
178 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 else:
179 else:
98 # find profiledir by profile name:
180 # find profiledir by profile name:
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
181 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 security_dir = profile_dir.security_dir
182 security_dir = profile_dir.security_dir
101
183
102 try:
184 try:
103 # first, try explicit name
185 # first, try explicit name
104 return filefind(filename, ['.', security_dir])
186 return filefind(filename, ['.', security_dir])
105 except IOError:
187 except IOError:
106 pass
188 pass
107
189
108 # not found by full name
190 # not found by full name
109
191
110 if '*' in filename:
192 if '*' in filename:
111 # given as a glob already
193 # given as a glob already
112 pat = filename
194 pat = filename
113 else:
195 else:
114 # accept any substring match
196 # accept any substring match
115 pat = '*%s*' % filename
197 pat = '*%s*' % filename
116 matches = glob.glob( os.path.join(security_dir, pat) )
198 matches = glob.glob( os.path.join(security_dir, pat) )
117 if not matches:
199 if not matches:
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
200 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 elif len(matches) == 1:
201 elif len(matches) == 1:
120 return matches[0]
202 return matches[0]
121 else:
203 else:
122 # get most recent match, by access time:
204 # get most recent match, by access time:
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
205 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124
206
207
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
208 def get_connection_info(connection_file=None, unpack=False, profile=None):
126 """Return the connection information for the current Kernel.
209 """Return the connection information for the current Kernel.
127
210
128 Parameters
211 Parameters
129 ----------
212 ----------
130 connection_file : str [optional]
213 connection_file : str [optional]
131 The connection file to be used. Can be given by absolute path, or
214 The connection file to be used. Can be given by absolute path, or
132 IPython will search in the security directory of a given profile.
215 IPython will search in the security directory of a given profile.
133 If run from IPython,
216 If run from IPython,
134
217
135 If unspecified, the connection file for the currently running
218 If unspecified, the connection file for the currently running
136 IPython Kernel will be used, which is only allowed from inside a kernel.
219 IPython Kernel will be used, which is only allowed from inside a kernel.
137 unpack : bool [default: False]
220 unpack : bool [default: False]
138 if True, return the unpacked dict, otherwise just the string contents
221 if True, return the unpacked dict, otherwise just the string contents
139 of the file.
222 of the file.
140 profile : str [optional]
223 profile : str [optional]
141 The name of the profile to use when searching for the connection file,
224 The name of the profile to use when searching for the connection file,
142 if different from the current IPython session or 'default'.
225 if different from the current IPython session or 'default'.
143
226
144
227
145 Returns
228 Returns
146 -------
229 -------
147 The connection dictionary of the current kernel, as string or dict,
230 The connection dictionary of the current kernel, as string or dict,
148 depending on `unpack`.
231 depending on `unpack`.
149 """
232 """
150 if connection_file is None:
233 if connection_file is None:
151 # get connection file from current kernel
234 # get connection file from current kernel
152 cf = get_connection_file()
235 cf = get_connection_file()
153 else:
236 else:
154 # connection file specified, allow shortnames:
237 # connection file specified, allow shortnames:
155 cf = find_connection_file(connection_file, profile=profile)
238 cf = find_connection_file(connection_file, profile=profile)
156
239
157 with open(cf) as f:
240 with open(cf) as f:
158 info = f.read()
241 info = f.read()
159
242
160 if unpack:
243 if unpack:
161 info = json.loads(info)
244 info = json.loads(info)
162 # ensure key is bytes:
245 # ensure key is bytes:
163 info['key'] = str_to_bytes(info.get('key', ''))
246 info['key'] = str_to_bytes(info.get('key', ''))
164 return info
247 return info
165
248
249
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
250 def connect_qtconsole(connection_file=None, argv=None, profile=None):
167 """Connect a qtconsole to the current kernel.
251 """Connect a qtconsole to the current kernel.
168
252
169 This is useful for connecting a second qtconsole to a kernel, or to a
253 This is useful for connecting a second qtconsole to a kernel, or to a
170 local notebook.
254 local notebook.
171
255
172 Parameters
256 Parameters
173 ----------
257 ----------
174 connection_file : str [optional]
258 connection_file : str [optional]
175 The connection file to be used. Can be given by absolute path, or
259 The connection file to be used. Can be given by absolute path, or
176 IPython will search in the security directory of a given profile.
260 IPython will search in the security directory of a given profile.
177 If run from IPython,
261 If run from IPython,
178
262
179 If unspecified, the connection file for the currently running
263 If unspecified, the connection file for the currently running
180 IPython Kernel will be used, which is only allowed from inside a kernel.
264 IPython Kernel will be used, which is only allowed from inside a kernel.
181 argv : list [optional]
265 argv : list [optional]
182 Any extra args to be passed to the console.
266 Any extra args to be passed to the console.
183 profile : str [optional]
267 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
268 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
269 if different from the current IPython session or 'default'.
186
270
187
271
188 Returns
272 Returns
189 -------
273 -------
190 subprocess.Popen instance running the qtconsole frontend
274 subprocess.Popen instance running the qtconsole frontend
191 """
275 """
192 argv = [] if argv is None else argv
276 argv = [] if argv is None else argv
193
277
194 if connection_file is None:
278 if connection_file is None:
195 # get connection file from current kernel
279 # get connection file from current kernel
196 cf = get_connection_file()
280 cf = get_connection_file()
197 else:
281 else:
198 cf = find_connection_file(connection_file, profile=profile)
282 cf = find_connection_file(connection_file, profile=profile)
199
283
200 cmd = ';'.join([
284 cmd = ';'.join([
201 "from IPython.frontend.qt.console import qtconsoleapp",
285 "from IPython.frontend.qt.console import qtconsoleapp",
202 "qtconsoleapp.main()"
286 "qtconsoleapp.main()"
203 ])
287 ])
204
288
205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
206
290
291
207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
292 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
208 """tunnel connections to a kernel via ssh
293 """tunnel connections to a kernel via ssh
209
294
210 This will open four SSH tunnels from localhost on this machine to the
295 This will open four SSH tunnels from localhost on this machine to the
211 ports associated with the kernel. They can be either direct
296 ports associated with the kernel. They can be either direct
212 localhost-localhost tunnels, or if an intermediate server is necessary,
297 localhost-localhost tunnels, or if an intermediate server is necessary,
213 the kernel must be listening on a public IP.
298 the kernel must be listening on a public IP.
214
299
215 Parameters
300 Parameters
216 ----------
301 ----------
217 connection_info : dict or str (path)
302 connection_info : dict or str (path)
218 Either a connection dict, or the path to a JSON connection file
303 Either a connection dict, or the path to a JSON connection file
219 sshserver : str
304 sshserver : str
220 The ssh sever to use to tunnel to the kernel. Can be a full
305 The ssh sever to use to tunnel to the kernel. Can be a full
221 `user@server:port` string. ssh config aliases are respected.
306 `user@server:port` string. ssh config aliases are respected.
222 sshkey : str [optional]
307 sshkey : str [optional]
223 Path to file containing ssh key to use for authentication.
308 Path to file containing ssh key to use for authentication.
224 Only necessary if your ssh config does not already associate
309 Only necessary if your ssh config does not already associate
225 a keyfile with the host.
310 a keyfile with the host.
226
311
227 Returns
312 Returns
228 -------
313 -------
229
314
230 (shell, iopub, stdin, hb) : ints
315 (shell, iopub, stdin, hb) : ints
231 The four ports on localhost that have been forwarded to the kernel.
316 The four ports on localhost that have been forwarded to the kernel.
232 """
317 """
233 if isinstance(connection_info, basestring):
318 if isinstance(connection_info, basestring):
234 # it's a path, unpack it
319 # it's a path, unpack it
235 with open(connection_info) as f:
320 with open(connection_info) as f:
236 connection_info = json.loads(f.read())
321 connection_info = json.loads(f.read())
237
322
238 cf = connection_info
323 cf = connection_info
239
324
240 lports = tunnel.select_random_ports(4)
325 lports = tunnel.select_random_ports(4)
241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
326 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
242
327
243 remote_ip = cf['ip']
328 remote_ip = cf['ip']
244
329
245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
330 if tunnel.try_passwordless_ssh(sshserver, sshkey):
246 password=False
331 password=False
247 else:
332 else:
248 password = getpass("SSH Password for %s: "%sshserver)
333 password = getpass("SSH Password for %s: "%sshserver)
249
334
250 for lp,rp in zip(lports, rports):
335 for lp,rp in zip(lports, rports):
251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
336 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
252
337
253 return tuple(lports)
338 return tuple(lports)
254
339
340
341 #-----------------------------------------------------------------------------
342 # Launching Kernels
343 #-----------------------------------------------------------------------------
255
344
256 def swallow_argv(argv, aliases=None, flags=None):
345 def swallow_argv(argv, aliases=None, flags=None):
257 """strip frontend-specific aliases and flags from an argument list
346 """strip frontend-specific aliases and flags from an argument list
258
347
259 For use primarily in frontend apps that want to pass a subset of command-line
348 For use primarily in frontend apps that want to pass a subset of command-line
260 arguments through to a subprocess, where frontend-specific flags and aliases
349 arguments through to a subprocess, where frontend-specific flags and aliases
261 should be removed from the list.
350 should be removed from the list.
262
351
263 Parameters
352 Parameters
264 ----------
353 ----------
265
354
266 argv : list(str)
355 argv : list(str)
267 The starting argv, to be filtered
356 The starting argv, to be filtered
268 aliases : container of aliases (dict, list, set, etc.)
357 aliases : container of aliases (dict, list, set, etc.)
269 The frontend-specific aliases to be removed
358 The frontend-specific aliases to be removed
270 flags : container of flags (dict, list, set, etc.)
359 flags : container of flags (dict, list, set, etc.)
271 The frontend-specific flags to be removed
360 The frontend-specific flags to be removed
272
361
273 Returns
362 Returns
274 -------
363 -------
275
364
276 argv : list(str)
365 argv : list(str)
277 The argv list, excluding flags and aliases that have been stripped
366 The argv list, excluding flags and aliases that have been stripped
278 """
367 """
279
368
280 if aliases is None:
369 if aliases is None:
281 aliases = set()
370 aliases = set()
282 if flags is None:
371 if flags is None:
283 flags = set()
372 flags = set()
284
373
285 stripped = list(argv) # copy
374 stripped = list(argv) # copy
286
375
287 swallow_next = False
376 swallow_next = False
288 was_flag = False
377 was_flag = False
289 for a in argv:
378 for a in argv:
290 if swallow_next:
379 if swallow_next:
291 swallow_next = False
380 swallow_next = False
292 # last arg was an alias, remove the next one
381 # last arg was an alias, remove the next one
293 # *unless* the last alias has a no-arg flag version, in which
382 # *unless* the last alias has a no-arg flag version, in which
294 # case, don't swallow the next arg if it's also a flag:
383 # case, don't swallow the next arg if it's also a flag:
295 if not (was_flag and a.startswith('-')):
384 if not (was_flag and a.startswith('-')):
296 stripped.remove(a)
385 stripped.remove(a)
297 continue
386 continue
298 if a.startswith('-'):
387 if a.startswith('-'):
299 split = a.lstrip('-').split('=')
388 split = a.lstrip('-').split('=')
300 alias = split[0]
389 alias = split[0]
301 if alias in aliases:
390 if alias in aliases:
302 stripped.remove(a)
391 stripped.remove(a)
303 if len(split) == 1:
392 if len(split) == 1:
304 # alias passed with arg via space
393 # alias passed with arg via space
305 swallow_next = True
394 swallow_next = True
306 # could have been a flag that matches an alias, e.g. `existing`
395 # could have been a flag that matches an alias, e.g. `existing`
307 # in which case, we might not swallow the next arg
396 # in which case, we might not swallow the next arg
308 was_flag = alias in flags
397 was_flag = alias in flags
309 elif alias in flags and len(split) == 1:
398 elif alias in flags and len(split) == 1:
310 # strip flag, but don't swallow next, as flags don't take args
399 # strip flag, but don't swallow next, as flags don't take args
311 stripped.remove(a)
400 stripped.remove(a)
312
401
313 # return shortened list
402 # return shortened list
314 return stripped
403 return stripped
315
404
405
406 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
407 """Build Popen command list for launching an IPython kernel.
408
409 Parameters
410 ----------
411 code : str,
412 A string of Python code that imports and executes a kernel entry point.
413
414 executable : str, optional (default sys.executable)
415 The Python executable to use for the kernel process.
416
417 extra_arguments : list, optional
418 A list of extra arguments to pass when executing the launch code.
419
420 Returns
421 -------
422
423 A Popen command list
424 """
425
426 # Build the kernel launch command.
427 if executable is None:
428 executable = sys.executable
429 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
430 arguments.extend(extra_arguments)
431
432 # Spawn a kernel.
433 if sys.platform == 'win32':
434
435 # If the kernel is running on pythonw and stdout/stderr are not been
436 # re-directed, it will crash when more than 4KB of data is written to
437 # stdout or stderr. This is a bug that has been with Python for a very
438 # long time; see http://bugs.python.org/issue706263.
439 # A cleaner solution to this problem would be to pass os.devnull to
440 # Popen directly. Unfortunately, that does not work.
441 if executable.endswith('pythonw.exe'):
442 arguments.append('--no-stdout')
443 arguments.append('--no-stderr')
444
445 return arguments
446
447
448 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
449 independent=False,
450 cwd=None, ipython_kernel=True,
451 **kw
452 ):
453 """ Launches a localhost kernel, binding to the specified ports.
454
455 Parameters
456 ----------
457 cmd : Popen list,
458 A string of Python code that imports and executes a kernel entry point.
459
460 stdin, stdout, stderr : optional (default None)
461 Standards streams, as defined in subprocess.Popen.
462
463 independent : bool, optional (default False)
464 If set, the kernel process is guaranteed to survive if this process
465 dies. If not set, an effort is made to ensure that the kernel is killed
466 when this process dies. Note that in this case it is still good practice
467 to kill kernels manually before exiting.
468
469 cwd : path, optional
470 The working dir of the kernel process (default: cwd of this process).
471
472 ipython_kernel : bool, optional
473 Whether the kernel is an official IPython one,
474 and should get a bit of special treatment.
475
476 Returns
477 -------
478
479 Popen instance for the kernel subprocess
480 """
481
482 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
483 # are invalid. Unfortunately, there is in general no way to detect whether
484 # they are valid. The following two blocks redirect them to (temporary)
485 # pipes in certain important cases.
486
487 # If this process has been backgrounded, our stdin is invalid. Since there
488 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
489 # place this one safe and always redirect.
490 redirect_in = True
491 _stdin = PIPE if stdin is None else stdin
492
493 # If this process in running on pythonw, we know that stdin, stdout, and
494 # stderr are all invalid.
495 redirect_out = sys.executable.endswith('pythonw.exe')
496 if redirect_out:
497 _stdout = PIPE if stdout is None else stdout
498 _stderr = PIPE if stderr is None else stderr
499 else:
500 _stdout, _stderr = stdout, stderr
501
502 # Spawn a kernel.
503 if sys.platform == 'win32':
504 from IPython.zmq.parentpoller import ParentPollerWindows
505 # Create a Win32 event for interrupting the kernel.
506 interrupt_event = ParentPollerWindows.create_interrupt_event()
507 if ipython_kernel:
508 cmd += [ '--interrupt=%i' % interrupt_event ]
509
510 # If the kernel is running on pythonw and stdout/stderr are not been
511 # re-directed, it will crash when more than 4KB of data is written to
512 # stdout or stderr. This is a bug that has been with Python for a very
513 # long time; see http://bugs.python.org/issue706263.
514 # A cleaner solution to this problem would be to pass os.devnull to
515 # Popen directly. Unfortunately, that does not work.
516 if cmd[0].endswith('pythonw.exe'):
517 if stdout is None:
518 cmd.append('--no-stdout')
519 if stderr is None:
520 cmd.append('--no-stderr')
521
522 # Launch the kernel process.
523 if independent:
524 proc = Popen(cmd,
525 creationflags=512, # CREATE_NEW_PROCESS_GROUP
526 stdin=_stdin, stdout=_stdout, stderr=_stderr)
527 else:
528 if ipython_kernel:
529 try:
530 from _winapi import DuplicateHandle, GetCurrentProcess, \
531 DUPLICATE_SAME_ACCESS
532 except:
533 from _subprocess import DuplicateHandle, GetCurrentProcess, \
534 DUPLICATE_SAME_ACCESS
535 pid = GetCurrentProcess()
536 handle = DuplicateHandle(pid, pid, pid, 0,
537 True, # Inheritable by new processes.
538 DUPLICATE_SAME_ACCESS)
539 cmd +=[ '--parent=%i' % handle ]
540
541
542 proc = Popen(cmd,
543 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
544
545 # Attach the interrupt event to the Popen objet so it can be used later.
546 proc.win32_interrupt_event = interrupt_event
547
548 else:
549 if independent:
550 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
551 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
552 else:
553 if ipython_kernel:
554 cmd += ['--parent=1']
555 proc = Popen(cmd,
556 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
557
558 # Clean up pipes created to work around Popen bug.
559 if redirect_in:
560 if stdin is None:
561 proc.stdin.close()
562 if redirect_out:
563 if stdout is None:
564 proc.stdout.close()
565 if stderr is None:
566 proc.stderr.close()
567
568 return proc
569
@@ -1,372 +1,372 b''
1 """An Application for launching a kernel
1 """An Application for launching a kernel
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * MinRK
5 * MinRK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
8 # Copyright (C) 2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING.txt, distributed as part of this software.
11 # the file COPYING.txt, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports
18 # Standard library imports
19 import atexit
19 import atexit
20 import json
20 import json
21 import os
21 import os
22 import sys
22 import sys
23 import signal
23 import signal
24
24
25 # System library imports
25 # System library imports
26 import zmq
26 import zmq
27 from zmq.eventloop import ioloop
27 from zmq.eventloop import ioloop
28
28
29 # IPython imports
29 # IPython imports
30 from IPython.core.ultratb import FormattedTB
30 from IPython.core.ultratb import FormattedTB
31 from IPython.core.application import (
31 from IPython.core.application import (
32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
33 )
33 )
34 from IPython.utils import io
34 from IPython.utils import io
35 from IPython.utils.localinterfaces import LOCALHOST
35 from IPython.utils.localinterfaces import LOCALHOST
36 from IPython.utils.path import filefind
36 from IPython.utils.path import filefind
37 from IPython.utils.py3compat import str_to_bytes
37 from IPython.utils.py3compat import str_to_bytes
38 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
40 DottedObjectName,
40 DottedObjectName,
41 )
41 )
42 from IPython.utils.importstring import import_item
42 from IPython.utils.importstring import import_item
43 from IPython.utils.kernel import write_connection_file
43 # local imports
44 # local imports
44 from IPython.zmq.entry_point import write_connection_file
45 from IPython.zmq.heartbeat import Heartbeat
45 from IPython.zmq.heartbeat import Heartbeat
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
47 from IPython.zmq.session import (
47 from IPython.zmq.session import (
48 Session, session_flags, session_aliases, default_secure,
48 Session, session_flags, session_aliases, default_secure,
49 )
49 )
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Flags and Aliases
53 # Flags and Aliases
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 kernel_aliases = dict(base_aliases)
56 kernel_aliases = dict(base_aliases)
57 kernel_aliases.update({
57 kernel_aliases.update({
58 'ip' : 'KernelApp.ip',
58 'ip' : 'KernelApp.ip',
59 'hb' : 'KernelApp.hb_port',
59 'hb' : 'KernelApp.hb_port',
60 'shell' : 'KernelApp.shell_port',
60 'shell' : 'KernelApp.shell_port',
61 'iopub' : 'KernelApp.iopub_port',
61 'iopub' : 'KernelApp.iopub_port',
62 'stdin' : 'KernelApp.stdin_port',
62 'stdin' : 'KernelApp.stdin_port',
63 'f' : 'KernelApp.connection_file',
63 'f' : 'KernelApp.connection_file',
64 'parent': 'KernelApp.parent',
64 'parent': 'KernelApp.parent',
65 'transport': 'KernelApp.transport',
65 'transport': 'KernelApp.transport',
66 })
66 })
67 if sys.platform.startswith('win'):
67 if sys.platform.startswith('win'):
68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
69
69
70 kernel_flags = dict(base_flags)
70 kernel_flags = dict(base_flags)
71 kernel_flags.update({
71 kernel_flags.update({
72 'no-stdout' : (
72 'no-stdout' : (
73 {'KernelApp' : {'no_stdout' : True}},
73 {'KernelApp' : {'no_stdout' : True}},
74 "redirect stdout to the null device"),
74 "redirect stdout to the null device"),
75 'no-stderr' : (
75 'no-stderr' : (
76 {'KernelApp' : {'no_stderr' : True}},
76 {'KernelApp' : {'no_stderr' : True}},
77 "redirect stderr to the null device"),
77 "redirect stderr to the null device"),
78 })
78 })
79
79
80 # inherit flags&aliases for Sessions
80 # inherit flags&aliases for Sessions
81 kernel_aliases.update(session_aliases)
81 kernel_aliases.update(session_aliases)
82 kernel_flags.update(session_flags)
82 kernel_flags.update(session_flags)
83
83
84
84
85
85
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 # Application class for starting a Kernel
87 # Application class for starting a Kernel
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89
89
90 class KernelApp(BaseIPythonApplication):
90 class KernelApp(BaseIPythonApplication):
91 name='ipkernel'
91 name='ipkernel'
92 aliases = Dict(kernel_aliases)
92 aliases = Dict(kernel_aliases)
93 flags = Dict(kernel_flags)
93 flags = Dict(kernel_flags)
94 classes = [Session]
94 classes = [Session]
95 # the kernel class, as an importstring
95 # the kernel class, as an importstring
96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
97 kernel = Any()
97 kernel = Any()
98 poller = Any() # don't restrict this even though current pollers are all Threads
98 poller = Any() # don't restrict this even though current pollers are all Threads
99 heartbeat = Instance(Heartbeat)
99 heartbeat = Instance(Heartbeat)
100 session = Instance('IPython.zmq.session.Session')
100 session = Instance('IPython.zmq.session.Session')
101 ports = Dict()
101 ports = Dict()
102
102
103 # inherit config file name from parent:
103 # inherit config file name from parent:
104 parent_appname = Unicode(config=True)
104 parent_appname = Unicode(config=True)
105 def _parent_appname_changed(self, name, old, new):
105 def _parent_appname_changed(self, name, old, new):
106 if self.config_file_specified:
106 if self.config_file_specified:
107 # it was manually specified, ignore
107 # it was manually specified, ignore
108 return
108 return
109 self.config_file_name = new.replace('-','_') + u'_config.py'
109 self.config_file_name = new.replace('-','_') + u'_config.py'
110 # don't let this count as specifying the config file
110 # don't let this count as specifying the config file
111 self.config_file_specified = False
111 self.config_file_specified = False
112
112
113 # connection info:
113 # connection info:
114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
115 ip = Unicode(config=True,
115 ip = Unicode(config=True,
116 help="Set the IP or interface on which the kernel will listen.")
116 help="Set the IP or interface on which the kernel will listen.")
117 def _ip_default(self):
117 def _ip_default(self):
118 if self.transport == 'ipc':
118 if self.transport == 'ipc':
119 if self.connection_file:
119 if self.connection_file:
120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
121 else:
121 else:
122 return 'kernel-ipc'
122 return 'kernel-ipc'
123 else:
123 else:
124 return LOCALHOST
124 return LOCALHOST
125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
129 connection_file = Unicode('', config=True,
129 connection_file = Unicode('', config=True,
130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
131
131
132 This file will contain the IP, ports, and authentication key needed to connect
132 This file will contain the IP, ports, and authentication key needed to connect
133 clients to this kernel. By default, this file will be created in the security dir
133 clients to this kernel. By default, this file will be created in the security dir
134 of the current profile, but can be specified by absolute path.
134 of the current profile, but can be specified by absolute path.
135 """)
135 """)
136 @property
136 @property
137 def abs_connection_file(self):
137 def abs_connection_file(self):
138 if os.path.basename(self.connection_file) == self.connection_file:
138 if os.path.basename(self.connection_file) == self.connection_file:
139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
140 else:
140 else:
141 return self.connection_file
141 return self.connection_file
142
142
143
143
144 # streams, etc.
144 # streams, etc.
145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
148 config=True, help="The importstring for the OutStream factory")
148 config=True, help="The importstring for the OutStream factory")
149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
150 config=True, help="The importstring for the DisplayHook factory")
150 config=True, help="The importstring for the DisplayHook factory")
151
151
152 # polling
152 # polling
153 parent = Integer(0, config=True,
153 parent = Integer(0, config=True,
154 help="""kill this process if its parent dies. On Windows, the argument
154 help="""kill this process if its parent dies. On Windows, the argument
155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
156 """)
156 """)
157 interrupt = Integer(0, config=True,
157 interrupt = Integer(0, config=True,
158 help="""ONLY USED ON WINDOWS
158 help="""ONLY USED ON WINDOWS
159 Interrupt this process when the parent is signaled.
159 Interrupt this process when the parent is signaled.
160 """)
160 """)
161
161
162 def init_crash_handler(self):
162 def init_crash_handler(self):
163 # Install minimal exception handling
163 # Install minimal exception handling
164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
165 ostream=sys.__stdout__)
165 ostream=sys.__stdout__)
166
166
167 def init_poller(self):
167 def init_poller(self):
168 if sys.platform == 'win32':
168 if sys.platform == 'win32':
169 if self.interrupt or self.parent:
169 if self.interrupt or self.parent:
170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
171 elif self.parent:
171 elif self.parent:
172 self.poller = ParentPollerUnix()
172 self.poller = ParentPollerUnix()
173
173
174 def _bind_socket(self, s, port):
174 def _bind_socket(self, s, port):
175 iface = '%s://%s' % (self.transport, self.ip)
175 iface = '%s://%s' % (self.transport, self.ip)
176 if self.transport == 'tcp':
176 if self.transport == 'tcp':
177 if port <= 0:
177 if port <= 0:
178 port = s.bind_to_random_port(iface)
178 port = s.bind_to_random_port(iface)
179 else:
179 else:
180 s.bind("tcp://%s:%i" % (self.ip, port))
180 s.bind("tcp://%s:%i" % (self.ip, port))
181 elif self.transport == 'ipc':
181 elif self.transport == 'ipc':
182 if port <= 0:
182 if port <= 0:
183 port = 1
183 port = 1
184 path = "%s-%i" % (self.ip, port)
184 path = "%s-%i" % (self.ip, port)
185 while os.path.exists(path):
185 while os.path.exists(path):
186 port = port + 1
186 port = port + 1
187 path = "%s-%i" % (self.ip, port)
187 path = "%s-%i" % (self.ip, port)
188 else:
188 else:
189 path = "%s-%i" % (self.ip, port)
189 path = "%s-%i" % (self.ip, port)
190 s.bind("ipc://%s" % path)
190 s.bind("ipc://%s" % path)
191 return port
191 return port
192
192
193 def load_connection_file(self):
193 def load_connection_file(self):
194 """load ip/port/hmac config from JSON connection file"""
194 """load ip/port/hmac config from JSON connection file"""
195 try:
195 try:
196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
197 except IOError:
197 except IOError:
198 self.log.debug("Connection file not found: %s", self.connection_file)
198 self.log.debug("Connection file not found: %s", self.connection_file)
199 # This means I own it, so I will clean it up:
199 # This means I own it, so I will clean it up:
200 atexit.register(self.cleanup_connection_file)
200 atexit.register(self.cleanup_connection_file)
201 return
201 return
202 self.log.debug(u"Loading connection file %s", fname)
202 self.log.debug(u"Loading connection file %s", fname)
203 with open(fname) as f:
203 with open(fname) as f:
204 s = f.read()
204 s = f.read()
205 cfg = json.loads(s)
205 cfg = json.loads(s)
206 self.transport = cfg.get('transport', self.transport)
206 self.transport = cfg.get('transport', self.transport)
207 if self.ip == self._ip_default() and 'ip' in cfg:
207 if self.ip == self._ip_default() and 'ip' in cfg:
208 # not overridden by config or cl_args
208 # not overridden by config or cl_args
209 self.ip = cfg['ip']
209 self.ip = cfg['ip']
210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
211 name = channel + '_port'
211 name = channel + '_port'
212 if getattr(self, name) == 0 and name in cfg:
212 if getattr(self, name) == 0 and name in cfg:
213 # not overridden by config or cl_args
213 # not overridden by config or cl_args
214 setattr(self, name, cfg[name])
214 setattr(self, name, cfg[name])
215 if 'key' in cfg:
215 if 'key' in cfg:
216 self.config.Session.key = str_to_bytes(cfg['key'])
216 self.config.Session.key = str_to_bytes(cfg['key'])
217
217
218 def write_connection_file(self):
218 def write_connection_file(self):
219 """write connection info to JSON file"""
219 """write connection info to JSON file"""
220 cf = self.abs_connection_file
220 cf = self.abs_connection_file
221 self.log.debug("Writing connection file: %s", cf)
221 self.log.debug("Writing connection file: %s", cf)
222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
224 iopub_port=self.iopub_port)
224 iopub_port=self.iopub_port)
225
225
226 def cleanup_connection_file(self):
226 def cleanup_connection_file(self):
227 cf = self.abs_connection_file
227 cf = self.abs_connection_file
228 self.log.debug("Cleaning up connection file: %s", cf)
228 self.log.debug("Cleaning up connection file: %s", cf)
229 try:
229 try:
230 os.remove(cf)
230 os.remove(cf)
231 except (IOError, OSError):
231 except (IOError, OSError):
232 pass
232 pass
233
233
234 self.cleanup_ipc_files()
234 self.cleanup_ipc_files()
235
235
236 def cleanup_ipc_files(self):
236 def cleanup_ipc_files(self):
237 """cleanup ipc files if we wrote them"""
237 """cleanup ipc files if we wrote them"""
238 if self.transport != 'ipc':
238 if self.transport != 'ipc':
239 return
239 return
240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
241 ipcfile = "%s-%i" % (self.ip, port)
241 ipcfile = "%s-%i" % (self.ip, port)
242 try:
242 try:
243 os.remove(ipcfile)
243 os.remove(ipcfile)
244 except (IOError, OSError):
244 except (IOError, OSError):
245 pass
245 pass
246
246
247 def init_connection_file(self):
247 def init_connection_file(self):
248 if not self.connection_file:
248 if not self.connection_file:
249 self.connection_file = "kernel-%s.json"%os.getpid()
249 self.connection_file = "kernel-%s.json"%os.getpid()
250 try:
250 try:
251 self.load_connection_file()
251 self.load_connection_file()
252 except Exception:
252 except Exception:
253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
254 self.exit(1)
254 self.exit(1)
255
255
256 def init_sockets(self):
256 def init_sockets(self):
257 # Create a context, a session, and the kernel sockets.
257 # Create a context, a session, and the kernel sockets.
258 self.log.info("Starting the kernel at pid: %i", os.getpid())
258 self.log.info("Starting the kernel at pid: %i", os.getpid())
259 context = zmq.Context.instance()
259 context = zmq.Context.instance()
260 # Uncomment this to try closing the context.
260 # Uncomment this to try closing the context.
261 # atexit.register(context.term)
261 # atexit.register(context.term)
262
262
263 self.shell_socket = context.socket(zmq.ROUTER)
263 self.shell_socket = context.socket(zmq.ROUTER)
264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
266
266
267 self.iopub_socket = context.socket(zmq.PUB)
267 self.iopub_socket = context.socket(zmq.PUB)
268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
270
270
271 self.stdin_socket = context.socket(zmq.ROUTER)
271 self.stdin_socket = context.socket(zmq.ROUTER)
272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
274
274
275 def init_heartbeat(self):
275 def init_heartbeat(self):
276 """start the heart beating"""
276 """start the heart beating"""
277 # heartbeat doesn't share context, because it mustn't be blocked
277 # heartbeat doesn't share context, because it mustn't be blocked
278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
279 hb_ctx = zmq.Context()
279 hb_ctx = zmq.Context()
280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
281 self.hb_port = self.heartbeat.port
281 self.hb_port = self.heartbeat.port
282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
283 self.heartbeat.start()
283 self.heartbeat.start()
284
284
285 # Helper to make it easier to connect to an existing kernel.
285 # Helper to make it easier to connect to an existing kernel.
286 # set log-level to critical, to make sure it is output
286 # set log-level to critical, to make sure it is output
287 self.log.critical("To connect another client to this kernel, use:")
287 self.log.critical("To connect another client to this kernel, use:")
288
288
289 def log_connection_info(self):
289 def log_connection_info(self):
290 """display connection info, and store ports"""
290 """display connection info, and store ports"""
291 basename = os.path.basename(self.connection_file)
291 basename = os.path.basename(self.connection_file)
292 if basename == self.connection_file or \
292 if basename == self.connection_file or \
293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
294 # use shortname
294 # use shortname
295 tail = basename
295 tail = basename
296 if self.profile != 'default':
296 if self.profile != 'default':
297 tail += " --profile %s" % self.profile
297 tail += " --profile %s" % self.profile
298 else:
298 else:
299 tail = self.connection_file
299 tail = self.connection_file
300 self.log.critical("--existing %s", tail)
300 self.log.critical("--existing %s", tail)
301
301
302
302
303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
304 stdin=self.stdin_port, hb=self.hb_port)
304 stdin=self.stdin_port, hb=self.hb_port)
305
305
306 def init_session(self):
306 def init_session(self):
307 """create our session object"""
307 """create our session object"""
308 default_secure(self.config)
308 default_secure(self.config)
309 self.session = Session(config=self.config, username=u'kernel')
309 self.session = Session(config=self.config, username=u'kernel')
310
310
311 def init_blackhole(self):
311 def init_blackhole(self):
312 """redirects stdout/stderr to devnull if necessary"""
312 """redirects stdout/stderr to devnull if necessary"""
313 if self.no_stdout or self.no_stderr:
313 if self.no_stdout or self.no_stderr:
314 blackhole = open(os.devnull, 'w')
314 blackhole = open(os.devnull, 'w')
315 if self.no_stdout:
315 if self.no_stdout:
316 sys.stdout = sys.__stdout__ = blackhole
316 sys.stdout = sys.__stdout__ = blackhole
317 if self.no_stderr:
317 if self.no_stderr:
318 sys.stderr = sys.__stderr__ = blackhole
318 sys.stderr = sys.__stderr__ = blackhole
319
319
320 def init_io(self):
320 def init_io(self):
321 """Redirect input streams and set a display hook."""
321 """Redirect input streams and set a display hook."""
322 if self.outstream_class:
322 if self.outstream_class:
323 outstream_factory = import_item(str(self.outstream_class))
323 outstream_factory = import_item(str(self.outstream_class))
324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
326 if self.displayhook_class:
326 if self.displayhook_class:
327 displayhook_factory = import_item(str(self.displayhook_class))
327 displayhook_factory = import_item(str(self.displayhook_class))
328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
329
329
330 def init_signal(self):
330 def init_signal(self):
331 signal.signal(signal.SIGINT, signal.SIG_IGN)
331 signal.signal(signal.SIGINT, signal.SIG_IGN)
332
332
333 def init_kernel(self):
333 def init_kernel(self):
334 """Create the Kernel object itself"""
334 """Create the Kernel object itself"""
335 kernel_factory = import_item(str(self.kernel_class))
335 kernel_factory = import_item(str(self.kernel_class))
336 self.kernel = kernel_factory(config=self.config, session=self.session,
336 self.kernel = kernel_factory(config=self.config, session=self.session,
337 shell_socket=self.shell_socket,
337 shell_socket=self.shell_socket,
338 iopub_socket=self.iopub_socket,
338 iopub_socket=self.iopub_socket,
339 stdin_socket=self.stdin_socket,
339 stdin_socket=self.stdin_socket,
340 log=self.log
340 log=self.log
341 )
341 )
342 self.kernel.record_ports(self.ports)
342 self.kernel.record_ports(self.ports)
343
343
344 @catch_config_error
344 @catch_config_error
345 def initialize(self, argv=None):
345 def initialize(self, argv=None):
346 super(KernelApp, self).initialize(argv)
346 super(KernelApp, self).initialize(argv)
347 self.init_blackhole()
347 self.init_blackhole()
348 self.init_connection_file()
348 self.init_connection_file()
349 self.init_session()
349 self.init_session()
350 self.init_poller()
350 self.init_poller()
351 self.init_sockets()
351 self.init_sockets()
352 self.init_heartbeat()
352 self.init_heartbeat()
353 # writing/displaying connection info must be *after* init_sockets/heartbeat
353 # writing/displaying connection info must be *after* init_sockets/heartbeat
354 self.log_connection_info()
354 self.log_connection_info()
355 self.write_connection_file()
355 self.write_connection_file()
356 self.init_io()
356 self.init_io()
357 self.init_signal()
357 self.init_signal()
358 self.init_kernel()
358 self.init_kernel()
359 # flush stdout/stderr, so that anything written to these streams during
359 # flush stdout/stderr, so that anything written to these streams during
360 # initialization do not get associated with the first execution request
360 # initialization do not get associated with the first execution request
361 sys.stdout.flush()
361 sys.stdout.flush()
362 sys.stderr.flush()
362 sys.stderr.flush()
363
363
364 def start(self):
364 def start(self):
365 if self.poller is not None:
365 if self.poller is not None:
366 self.poller.start()
366 self.poller.start()
367 self.kernel.start()
367 self.kernel.start()
368 try:
368 try:
369 ioloop.IOLoop.instance().start()
369 ioloop.IOLoop.instance().start()
370 except KeyboardInterrupt:
370 except KeyboardInterrupt:
371 pass
371 pass
372
372
@@ -1,1134 +1,1129 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 import errno
20 import errno
21 import json
21 import json
22 from subprocess import Popen
22 from subprocess import Popen
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 from threading import Thread
26 from threading import Thread
27 import time
27 import time
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 # during garbage collection of threads at exit:
32 # during garbage collection of threads at exit:
33 from zmq import ZMQError
33 from zmq import ZMQError
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports.
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 )
41 )
42 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.zmq.entry_point import (
43 from IPython.utils.kernel import (
44 write_connection_file,
44 write_connection_file,
45 make_ipkernel_cmd,
45 make_ipkernel_cmd,
46 launch_kernel,
46 launch_kernel,
47 )
47 )
48 from session import Session
48 from session import Session
49 from IPython.zmq.kernelmanagerabc import (
49 from IPython.zmq.kernelmanagerabc import (
50 ShellChannelABC, IOPubChannelABC,
50 ShellChannelABC, IOPubChannelABC,
51 HBChannelABC, StdInChannelABC,
51 HBChannelABC, StdInChannelABC,
52 KernelManagerABC
52 KernelManagerABC
53 )
53 )
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Constants and exceptions
57 # Constants and exceptions
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class InvalidPortNumber(Exception):
60 class InvalidPortNumber(Exception):
61 pass
61 pass
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Utility functions
64 # Utility functions
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # some utilities to validate message structure, these might get moved elsewhere
67 # some utilities to validate message structure, these might get moved elsewhere
68 # if they prove to have more generic utility
68 # if they prove to have more generic utility
69
69
70 def validate_string_list(lst):
70 def validate_string_list(lst):
71 """Validate that the input is a list of strings.
71 """Validate that the input is a list of strings.
72
72
73 Raises ValueError if not."""
73 Raises ValueError if not."""
74 if not isinstance(lst, list):
74 if not isinstance(lst, list):
75 raise ValueError('input %r must be a list' % lst)
75 raise ValueError('input %r must be a list' % lst)
76 for x in lst:
76 for x in lst:
77 if not isinstance(x, basestring):
77 if not isinstance(x, basestring):
78 raise ValueError('element %r in list must be a string' % x)
78 raise ValueError('element %r in list must be a string' % x)
79
79
80
80
81 def validate_string_dict(dct):
81 def validate_string_dict(dct):
82 """Validate that the input is a dict with string keys and values.
82 """Validate that the input is a dict with string keys and values.
83
83
84 Raises ValueError if not."""
84 Raises ValueError if not."""
85 for k,v in dct.iteritems():
85 for k,v in dct.iteritems():
86 if not isinstance(k, basestring):
86 if not isinstance(k, basestring):
87 raise ValueError('key %r in dict must be a string' % k)
87 raise ValueError('key %r in dict must be a string' % k)
88 if not isinstance(v, basestring):
88 if not isinstance(v, basestring):
89 raise ValueError('value %r in dict must be a string' % v)
89 raise ValueError('value %r in dict must be a string' % v)
90
90
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # ZMQ Socket Channel classes
93 # ZMQ Socket Channel classes
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 class ZMQSocketChannel(Thread):
96 class ZMQSocketChannel(Thread):
97 """The base class for the channels that use ZMQ sockets."""
97 """The base class for the channels that use ZMQ sockets."""
98 context = None
98 context = None
99 session = None
99 session = None
100 socket = None
100 socket = None
101 ioloop = None
101 ioloop = None
102 stream = None
102 stream = None
103 _address = None
103 _address = None
104 _exiting = False
104 _exiting = False
105
105
106 def __init__(self, context, session, address):
106 def __init__(self, context, session, address):
107 """Create a channel.
107 """Create a channel.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 context : :class:`zmq.Context`
111 context : :class:`zmq.Context`
112 The ZMQ context to use.
112 The ZMQ context to use.
113 session : :class:`session.Session`
113 session : :class:`session.Session`
114 The session to use.
114 The session to use.
115 address : zmq url
115 address : zmq url
116 Standard (ip, port) tuple that the kernel is listening on.
116 Standard (ip, port) tuple that the kernel is listening on.
117 """
117 """
118 super(ZMQSocketChannel, self).__init__()
118 super(ZMQSocketChannel, self).__init__()
119 self.daemon = True
119 self.daemon = True
120
120
121 self.context = context
121 self.context = context
122 self.session = session
122 self.session = session
123 if isinstance(address, tuple):
123 if isinstance(address, tuple):
124 if address[1] == 0:
124 if address[1] == 0:
125 message = 'The port number for a channel cannot be 0.'
125 message = 'The port number for a channel cannot be 0.'
126 raise InvalidPortNumber(message)
126 raise InvalidPortNumber(message)
127 address = "tcp://%s:%i" % address
127 address = "tcp://%s:%i" % address
128 self._address = address
128 self._address = address
129 atexit.register(self._notice_exit)
129 atexit.register(self._notice_exit)
130
130
131 def _notice_exit(self):
131 def _notice_exit(self):
132 self._exiting = True
132 self._exiting = True
133
133
134 def _run_loop(self):
134 def _run_loop(self):
135 """Run my loop, ignoring EINTR events in the poller"""
135 """Run my loop, ignoring EINTR events in the poller"""
136 while True:
136 while True:
137 try:
137 try:
138 self.ioloop.start()
138 self.ioloop.start()
139 except ZMQError as e:
139 except ZMQError as e:
140 if e.errno == errno.EINTR:
140 if e.errno == errno.EINTR:
141 continue
141 continue
142 else:
142 else:
143 raise
143 raise
144 except Exception:
144 except Exception:
145 if self._exiting:
145 if self._exiting:
146 break
146 break
147 else:
147 else:
148 raise
148 raise
149 else:
149 else:
150 break
150 break
151
151
152 def stop(self):
152 def stop(self):
153 """Stop the channel's event loop and join its thread.
153 """Stop the channel's event loop and join its thread.
154
154
155 This calls :method:`Thread.join` and returns when the thread
155 This calls :method:`Thread.join` and returns when the thread
156 terminates. :class:`RuntimeError` will be raised if
156 terminates. :class:`RuntimeError` will be raised if
157 :method:`self.start` is called again.
157 :method:`self.start` is called again.
158 """
158 """
159 self.join()
159 self.join()
160
160
161 @property
161 @property
162 def address(self):
162 def address(self):
163 """Get the channel's address as a zmq url string.
163 """Get the channel's address as a zmq url string.
164
164
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 """
166 """
167 return self._address
167 return self._address
168
168
169 def _queue_send(self, msg):
169 def _queue_send(self, msg):
170 """Queue a message to be sent from the IOLoop's thread.
170 """Queue a message to be sent from the IOLoop's thread.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 msg : message to send
174 msg : message to send
175
175
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 thread control of the action.
177 thread control of the action.
178 """
178 """
179 def thread_send():
179 def thread_send():
180 self.session.send(self.stream, msg)
180 self.session.send(self.stream, msg)
181 self.ioloop.add_callback(thread_send)
181 self.ioloop.add_callback(thread_send)
182
182
183 def _handle_recv(self, msg):
183 def _handle_recv(self, msg):
184 """Callback for stream.on_recv.
184 """Callback for stream.on_recv.
185
185
186 Unpacks message, and calls handlers with it.
186 Unpacks message, and calls handlers with it.
187 """
187 """
188 ident,smsg = self.session.feed_identities(msg)
188 ident,smsg = self.session.feed_identities(msg)
189 self.call_handlers(self.session.unserialize(smsg))
189 self.call_handlers(self.session.unserialize(smsg))
190
190
191
191
192
192
193 class ShellChannel(ZMQSocketChannel):
193 class ShellChannel(ZMQSocketChannel):
194 """The shell channel for issuing request/replies to the kernel."""
194 """The shell channel for issuing request/replies to the kernel."""
195
195
196 command_queue = None
196 command_queue = None
197 # flag for whether execute requests should be allowed to call raw_input:
197 # flag for whether execute requests should be allowed to call raw_input:
198 allow_stdin = True
198 allow_stdin = True
199
199
200 def __init__(self, context, session, address):
200 def __init__(self, context, session, address):
201 super(ShellChannel, self).__init__(context, session, address)
201 super(ShellChannel, self).__init__(context, session, address)
202 self.ioloop = ioloop.IOLoop()
202 self.ioloop = ioloop.IOLoop()
203
203
204 def run(self):
204 def run(self):
205 """The thread's main activity. Call start() instead."""
205 """The thread's main activity. Call start() instead."""
206 self.socket = self.context.socket(zmq.DEALER)
206 self.socket = self.context.socket(zmq.DEALER)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 self.socket.connect(self.address)
208 self.socket.connect(self.address)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 self.stream.on_recv(self._handle_recv)
210 self.stream.on_recv(self._handle_recv)
211 self._run_loop()
211 self._run_loop()
212 try:
212 try:
213 self.socket.close()
213 self.socket.close()
214 except:
214 except:
215 pass
215 pass
216
216
217 def stop(self):
217 def stop(self):
218 """Stop the channel's event loop and join its thread."""
218 """Stop the channel's event loop and join its thread."""
219 self.ioloop.stop()
219 self.ioloop.stop()
220 super(ShellChannel, self).stop()
220 super(ShellChannel, self).stop()
221
221
222 def call_handlers(self, msg):
222 def call_handlers(self, msg):
223 """This method is called in the ioloop thread when a message arrives.
223 """This method is called in the ioloop thread when a message arrives.
224
224
225 Subclasses should override this method to handle incoming messages.
225 Subclasses should override this method to handle incoming messages.
226 It is important to remember that this method is called in the thread
226 It is important to remember that this method is called in the thread
227 so that some logic must be done to ensure that the application leve
227 so that some logic must be done to ensure that the application leve
228 handlers are called in the application thread.
228 handlers are called in the application thread.
229 """
229 """
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231
231
232 def execute(self, code, silent=False, store_history=True,
232 def execute(self, code, silent=False, store_history=True,
233 user_variables=None, user_expressions=None, allow_stdin=None):
233 user_variables=None, user_expressions=None, allow_stdin=None):
234 """Execute code in the kernel.
234 """Execute code in the kernel.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 code : str
238 code : str
239 A string of Python code.
239 A string of Python code.
240
240
241 silent : bool, optional (default False)
241 silent : bool, optional (default False)
242 If set, the kernel will execute the code as quietly possible, and
242 If set, the kernel will execute the code as quietly possible, and
243 will force store_history to be False.
243 will force store_history to be False.
244
244
245 store_history : bool, optional (default True)
245 store_history : bool, optional (default True)
246 If set, the kernel will store command history. This is forced
246 If set, the kernel will store command history. This is forced
247 to be False if silent is True.
247 to be False if silent is True.
248
248
249 user_variables : list, optional
249 user_variables : list, optional
250 A list of variable names to pull from the user's namespace. They
250 A list of variable names to pull from the user's namespace. They
251 will come back as a dict with these names as keys and their
251 will come back as a dict with these names as keys and their
252 :func:`repr` as values.
252 :func:`repr` as values.
253
253
254 user_expressions : dict, optional
254 user_expressions : dict, optional
255 A dict mapping names to expressions to be evaluated in the user's
255 A dict mapping names to expressions to be evaluated in the user's
256 dict. The expression values are returned as strings formatted using
256 dict. The expression values are returned as strings formatted using
257 :func:`repr`.
257 :func:`repr`.
258
258
259 allow_stdin : bool, optional (default self.allow_stdin)
259 allow_stdin : bool, optional (default self.allow_stdin)
260 Flag for whether the kernel can send stdin requests to frontends.
260 Flag for whether the kernel can send stdin requests to frontends.
261
261
262 Some frontends (e.g. the Notebook) do not support stdin requests.
262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 If raw_input is called from code executed from such a frontend, a
263 If raw_input is called from code executed from such a frontend, a
264 StdinNotImplementedError will be raised.
264 StdinNotImplementedError will be raised.
265
265
266 Returns
266 Returns
267 -------
267 -------
268 The msg_id of the message sent.
268 The msg_id of the message sent.
269 """
269 """
270 if user_variables is None:
270 if user_variables is None:
271 user_variables = []
271 user_variables = []
272 if user_expressions is None:
272 if user_expressions is None:
273 user_expressions = {}
273 user_expressions = {}
274 if allow_stdin is None:
274 if allow_stdin is None:
275 allow_stdin = self.allow_stdin
275 allow_stdin = self.allow_stdin
276
276
277
277
278 # Don't waste network traffic if inputs are invalid
278 # Don't waste network traffic if inputs are invalid
279 if not isinstance(code, basestring):
279 if not isinstance(code, basestring):
280 raise ValueError('code %r must be a string' % code)
280 raise ValueError('code %r must be a string' % code)
281 validate_string_list(user_variables)
281 validate_string_list(user_variables)
282 validate_string_dict(user_expressions)
282 validate_string_dict(user_expressions)
283
283
284 # Create class for content/msg creation. Related to, but possibly
284 # Create class for content/msg creation. Related to, but possibly
285 # not in Session.
285 # not in Session.
286 content = dict(code=code, silent=silent, store_history=store_history,
286 content = dict(code=code, silent=silent, store_history=store_history,
287 user_variables=user_variables,
287 user_variables=user_variables,
288 user_expressions=user_expressions,
288 user_expressions=user_expressions,
289 allow_stdin=allow_stdin,
289 allow_stdin=allow_stdin,
290 )
290 )
291 msg = self.session.msg('execute_request', content)
291 msg = self.session.msg('execute_request', content)
292 self._queue_send(msg)
292 self._queue_send(msg)
293 return msg['header']['msg_id']
293 return msg['header']['msg_id']
294
294
295 def complete(self, text, line, cursor_pos, block=None):
295 def complete(self, text, line, cursor_pos, block=None):
296 """Tab complete text in the kernel's namespace.
296 """Tab complete text in the kernel's namespace.
297
297
298 Parameters
298 Parameters
299 ----------
299 ----------
300 text : str
300 text : str
301 The text to complete.
301 The text to complete.
302 line : str
302 line : str
303 The full line of text that is the surrounding context for the
303 The full line of text that is the surrounding context for the
304 text to complete.
304 text to complete.
305 cursor_pos : int
305 cursor_pos : int
306 The position of the cursor in the line where the completion was
306 The position of the cursor in the line where the completion was
307 requested.
307 requested.
308 block : str, optional
308 block : str, optional
309 The full block of code in which the completion is being requested.
309 The full block of code in which the completion is being requested.
310
310
311 Returns
311 Returns
312 -------
312 -------
313 The msg_id of the message sent.
313 The msg_id of the message sent.
314 """
314 """
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 msg = self.session.msg('complete_request', content)
316 msg = self.session.msg('complete_request', content)
317 self._queue_send(msg)
317 self._queue_send(msg)
318 return msg['header']['msg_id']
318 return msg['header']['msg_id']
319
319
320 def object_info(self, oname, detail_level=0):
320 def object_info(self, oname, detail_level=0):
321 """Get metadata information about an object in the kernel's namespace.
321 """Get metadata information about an object in the kernel's namespace.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 oname : str
325 oname : str
326 A string specifying the object name.
326 A string specifying the object name.
327 detail_level : int, optional
327 detail_level : int, optional
328 The level of detail for the introspection (0-2)
328 The level of detail for the introspection (0-2)
329
329
330 Returns
330 Returns
331 -------
331 -------
332 The msg_id of the message sent.
332 The msg_id of the message sent.
333 """
333 """
334 content = dict(oname=oname, detail_level=detail_level)
334 content = dict(oname=oname, detail_level=detail_level)
335 msg = self.session.msg('object_info_request', content)
335 msg = self.session.msg('object_info_request', content)
336 self._queue_send(msg)
336 self._queue_send(msg)
337 return msg['header']['msg_id']
337 return msg['header']['msg_id']
338
338
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 """Get entries from the kernel's history list.
340 """Get entries from the kernel's history list.
341
341
342 Parameters
342 Parameters
343 ----------
343 ----------
344 raw : bool
344 raw : bool
345 If True, return the raw input.
345 If True, return the raw input.
346 output : bool
346 output : bool
347 If True, then return the output as well.
347 If True, then return the output as well.
348 hist_access_type : str
348 hist_access_type : str
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 or 'search' (fill in pattern param).
350 or 'search' (fill in pattern param).
351
351
352 session : int
352 session : int
353 For a range request, the session from which to get lines. Session
353 For a range request, the session from which to get lines. Session
354 numbers are positive integers; negative ones count back from the
354 numbers are positive integers; negative ones count back from the
355 current session.
355 current session.
356 start : int
356 start : int
357 The first line number of a history range.
357 The first line number of a history range.
358 stop : int
358 stop : int
359 The final (excluded) line number of a history range.
359 The final (excluded) line number of a history range.
360
360
361 n : int
361 n : int
362 The number of lines of history to get for a tail request.
362 The number of lines of history to get for a tail request.
363
363
364 pattern : str
364 pattern : str
365 The glob-syntax pattern for a search request.
365 The glob-syntax pattern for a search request.
366
366
367 Returns
367 Returns
368 -------
368 -------
369 The msg_id of the message sent.
369 The msg_id of the message sent.
370 """
370 """
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 **kwargs)
372 **kwargs)
373 msg = self.session.msg('history_request', content)
373 msg = self.session.msg('history_request', content)
374 self._queue_send(msg)
374 self._queue_send(msg)
375 return msg['header']['msg_id']
375 return msg['header']['msg_id']
376
376
377 def kernel_info(self):
377 def kernel_info(self):
378 """Request kernel info."""
378 """Request kernel info."""
379 msg = self.session.msg('kernel_info_request')
379 msg = self.session.msg('kernel_info_request')
380 self._queue_send(msg)
380 self._queue_send(msg)
381 return msg['header']['msg_id']
381 return msg['header']['msg_id']
382
382
383 def shutdown(self, restart=False):
383 def shutdown(self, restart=False):
384 """Request an immediate kernel shutdown.
384 """Request an immediate kernel shutdown.
385
385
386 Upon receipt of the (empty) reply, client code can safely assume that
386 Upon receipt of the (empty) reply, client code can safely assume that
387 the kernel has shut down and it's safe to forcefully terminate it if
387 the kernel has shut down and it's safe to forcefully terminate it if
388 it's still alive.
388 it's still alive.
389
389
390 The kernel will send the reply via a function registered with Python's
390 The kernel will send the reply via a function registered with Python's
391 atexit module, ensuring it's truly done as the kernel is done with all
391 atexit module, ensuring it's truly done as the kernel is done with all
392 normal operation.
392 normal operation.
393 """
393 """
394 # Send quit message to kernel. Once we implement kernel-side setattr,
394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # this should probably be done that way, but for now this will do.
395 # this should probably be done that way, but for now this will do.
396 msg = self.session.msg('shutdown_request', {'restart':restart})
396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 self._queue_send(msg)
397 self._queue_send(msg)
398 return msg['header']['msg_id']
398 return msg['header']['msg_id']
399
399
400
400
401
401
402 class IOPubChannel(ZMQSocketChannel):
402 class IOPubChannel(ZMQSocketChannel):
403 """The iopub channel which listens for messages that the kernel publishes.
403 """The iopub channel which listens for messages that the kernel publishes.
404
404
405 This channel is where all output is published to frontends.
405 This channel is where all output is published to frontends.
406 """
406 """
407
407
408 def __init__(self, context, session, address):
408 def __init__(self, context, session, address):
409 super(IOPubChannel, self).__init__(context, session, address)
409 super(IOPubChannel, self).__init__(context, session, address)
410 self.ioloop = ioloop.IOLoop()
410 self.ioloop = ioloop.IOLoop()
411
411
412 def run(self):
412 def run(self):
413 """The thread's main activity. Call start() instead."""
413 """The thread's main activity. Call start() instead."""
414 self.socket = self.context.socket(zmq.SUB)
414 self.socket = self.context.socket(zmq.SUB)
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 self.socket.connect(self.address)
417 self.socket.connect(self.address)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 self.stream.on_recv(self._handle_recv)
419 self.stream.on_recv(self._handle_recv)
420 self._run_loop()
420 self._run_loop()
421 try:
421 try:
422 self.socket.close()
422 self.socket.close()
423 except:
423 except:
424 pass
424 pass
425
425
426 def stop(self):
426 def stop(self):
427 """Stop the channel's event loop and join its thread."""
427 """Stop the channel's event loop and join its thread."""
428 self.ioloop.stop()
428 self.ioloop.stop()
429 super(IOPubChannel, self).stop()
429 super(IOPubChannel, self).stop()
430
430
431 def call_handlers(self, msg):
431 def call_handlers(self, msg):
432 """This method is called in the ioloop thread when a message arrives.
432 """This method is called in the ioloop thread when a message arrives.
433
433
434 Subclasses should override this method to handle incoming messages.
434 Subclasses should override this method to handle incoming messages.
435 It is important to remember that this method is called in the thread
435 It is important to remember that this method is called in the thread
436 so that some logic must be done to ensure that the application leve
436 so that some logic must be done to ensure that the application leve
437 handlers are called in the application thread.
437 handlers are called in the application thread.
438 """
438 """
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440
440
441 def flush(self, timeout=1.0):
441 def flush(self, timeout=1.0):
442 """Immediately processes all pending messages on the iopub channel.
442 """Immediately processes all pending messages on the iopub channel.
443
443
444 Callers should use this method to ensure that :method:`call_handlers`
444 Callers should use this method to ensure that :method:`call_handlers`
445 has been called for all messages that have been received on the
445 has been called for all messages that have been received on the
446 0MQ SUB socket of this channel.
446 0MQ SUB socket of this channel.
447
447
448 This method is thread safe.
448 This method is thread safe.
449
449
450 Parameters
450 Parameters
451 ----------
451 ----------
452 timeout : float, optional
452 timeout : float, optional
453 The maximum amount of time to spend flushing, in seconds. The
453 The maximum amount of time to spend flushing, in seconds. The
454 default is one second.
454 default is one second.
455 """
455 """
456 # We do the IOLoop callback process twice to ensure that the IOLoop
456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 # gets to perform at least one full poll.
457 # gets to perform at least one full poll.
458 stop_time = time.time() + timeout
458 stop_time = time.time() + timeout
459 for i in xrange(2):
459 for i in xrange(2):
460 self._flushed = False
460 self._flushed = False
461 self.ioloop.add_callback(self._flush)
461 self.ioloop.add_callback(self._flush)
462 while not self._flushed and time.time() < stop_time:
462 while not self._flushed and time.time() < stop_time:
463 time.sleep(0.01)
463 time.sleep(0.01)
464
464
465 def _flush(self):
465 def _flush(self):
466 """Callback for :method:`self.flush`."""
466 """Callback for :method:`self.flush`."""
467 self.stream.flush()
467 self.stream.flush()
468 self._flushed = True
468 self._flushed = True
469
469
470
470
471 class StdInChannel(ZMQSocketChannel):
471 class StdInChannel(ZMQSocketChannel):
472 """The stdin channel to handle raw_input requests that the kernel makes."""
472 """The stdin channel to handle raw_input requests that the kernel makes."""
473
473
474 msg_queue = None
474 msg_queue = None
475
475
476 def __init__(self, context, session, address):
476 def __init__(self, context, session, address):
477 super(StdInChannel, self).__init__(context, session, address)
477 super(StdInChannel, self).__init__(context, session, address)
478 self.ioloop = ioloop.IOLoop()
478 self.ioloop = ioloop.IOLoop()
479
479
480 def run(self):
480 def run(self):
481 """The thread's main activity. Call start() instead."""
481 """The thread's main activity. Call start() instead."""
482 self.socket = self.context.socket(zmq.DEALER)
482 self.socket = self.context.socket(zmq.DEALER)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 self.socket.connect(self.address)
484 self.socket.connect(self.address)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 self.stream.on_recv(self._handle_recv)
486 self.stream.on_recv(self._handle_recv)
487 self._run_loop()
487 self._run_loop()
488 try:
488 try:
489 self.socket.close()
489 self.socket.close()
490 except:
490 except:
491 pass
491 pass
492
492
493 def stop(self):
493 def stop(self):
494 """Stop the channel's event loop and join its thread."""
494 """Stop the channel's event loop and join its thread."""
495 self.ioloop.stop()
495 self.ioloop.stop()
496 super(StdInChannel, self).stop()
496 super(StdInChannel, self).stop()
497
497
498 def call_handlers(self, msg):
498 def call_handlers(self, msg):
499 """This method is called in the ioloop thread when a message arrives.
499 """This method is called in the ioloop thread when a message arrives.
500
500
501 Subclasses should override this method to handle incoming messages.
501 Subclasses should override this method to handle incoming messages.
502 It is important to remember that this method is called in the thread
502 It is important to remember that this method is called in the thread
503 so that some logic must be done to ensure that the application leve
503 so that some logic must be done to ensure that the application leve
504 handlers are called in the application thread.
504 handlers are called in the application thread.
505 """
505 """
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507
507
508 def input(self, string):
508 def input(self, string):
509 """Send a string of raw input to the kernel."""
509 """Send a string of raw input to the kernel."""
510 content = dict(value=string)
510 content = dict(value=string)
511 msg = self.session.msg('input_reply', content)
511 msg = self.session.msg('input_reply', content)
512 self._queue_send(msg)
512 self._queue_send(msg)
513
513
514
514
515 class HBChannel(ZMQSocketChannel):
515 class HBChannel(ZMQSocketChannel):
516 """The heartbeat channel which monitors the kernel heartbeat.
516 """The heartbeat channel which monitors the kernel heartbeat.
517
517
518 Note that the heartbeat channel is paused by default. As long as you start
518 Note that the heartbeat channel is paused by default. As long as you start
519 this channel, the kernel manager will ensure that it is paused and un-paused
519 this channel, the kernel manager will ensure that it is paused and un-paused
520 as appropriate.
520 as appropriate.
521 """
521 """
522
522
523 time_to_dead = 3.0
523 time_to_dead = 3.0
524 socket = None
524 socket = None
525 poller = None
525 poller = None
526 _running = None
526 _running = None
527 _pause = None
527 _pause = None
528 _beating = None
528 _beating = None
529
529
530 def __init__(self, context, session, address):
530 def __init__(self, context, session, address):
531 super(HBChannel, self).__init__(context, session, address)
531 super(HBChannel, self).__init__(context, session, address)
532 self._running = False
532 self._running = False
533 self._pause =True
533 self._pause =True
534 self.poller = zmq.Poller()
534 self.poller = zmq.Poller()
535
535
536 def _create_socket(self):
536 def _create_socket(self):
537 if self.socket is not None:
537 if self.socket is not None:
538 # close previous socket, before opening a new one
538 # close previous socket, before opening a new one
539 self.poller.unregister(self.socket)
539 self.poller.unregister(self.socket)
540 self.socket.close()
540 self.socket.close()
541 self.socket = self.context.socket(zmq.REQ)
541 self.socket = self.context.socket(zmq.REQ)
542 self.socket.setsockopt(zmq.LINGER, 0)
542 self.socket.setsockopt(zmq.LINGER, 0)
543 self.socket.connect(self.address)
543 self.socket.connect(self.address)
544
544
545 self.poller.register(self.socket, zmq.POLLIN)
545 self.poller.register(self.socket, zmq.POLLIN)
546
546
547 def _poll(self, start_time):
547 def _poll(self, start_time):
548 """poll for heartbeat replies until we reach self.time_to_dead.
548 """poll for heartbeat replies until we reach self.time_to_dead.
549
549
550 Ignores interrupts, and returns the result of poll(), which
550 Ignores interrupts, and returns the result of poll(), which
551 will be an empty list if no messages arrived before the timeout,
551 will be an empty list if no messages arrived before the timeout,
552 or the event tuple if there is a message to receive.
552 or the event tuple if there is a message to receive.
553 """
553 """
554
554
555 until_dead = self.time_to_dead - (time.time() - start_time)
555 until_dead = self.time_to_dead - (time.time() - start_time)
556 # ensure poll at least once
556 # ensure poll at least once
557 until_dead = max(until_dead, 1e-3)
557 until_dead = max(until_dead, 1e-3)
558 events = []
558 events = []
559 while True:
559 while True:
560 try:
560 try:
561 events = self.poller.poll(1000 * until_dead)
561 events = self.poller.poll(1000 * until_dead)
562 except ZMQError as e:
562 except ZMQError as e:
563 if e.errno == errno.EINTR:
563 if e.errno == errno.EINTR:
564 # ignore interrupts during heartbeat
564 # ignore interrupts during heartbeat
565 # this may never actually happen
565 # this may never actually happen
566 until_dead = self.time_to_dead - (time.time() - start_time)
566 until_dead = self.time_to_dead - (time.time() - start_time)
567 until_dead = max(until_dead, 1e-3)
567 until_dead = max(until_dead, 1e-3)
568 pass
568 pass
569 else:
569 else:
570 raise
570 raise
571 except Exception:
571 except Exception:
572 if self._exiting:
572 if self._exiting:
573 break
573 break
574 else:
574 else:
575 raise
575 raise
576 else:
576 else:
577 break
577 break
578 return events
578 return events
579
579
580 def run(self):
580 def run(self):
581 """The thread's main activity. Call start() instead."""
581 """The thread's main activity. Call start() instead."""
582 self._create_socket()
582 self._create_socket()
583 self._running = True
583 self._running = True
584 self._beating = True
584 self._beating = True
585
585
586 while self._running:
586 while self._running:
587 if self._pause:
587 if self._pause:
588 # just sleep, and skip the rest of the loop
588 # just sleep, and skip the rest of the loop
589 time.sleep(self.time_to_dead)
589 time.sleep(self.time_to_dead)
590 continue
590 continue
591
591
592 since_last_heartbeat = 0.0
592 since_last_heartbeat = 0.0
593 # io.rprint('Ping from HB channel') # dbg
593 # io.rprint('Ping from HB channel') # dbg
594 # no need to catch EFSM here, because the previous event was
594 # no need to catch EFSM here, because the previous event was
595 # either a recv or connect, which cannot be followed by EFSM
595 # either a recv or connect, which cannot be followed by EFSM
596 self.socket.send(b'ping')
596 self.socket.send(b'ping')
597 request_time = time.time()
597 request_time = time.time()
598 ready = self._poll(request_time)
598 ready = self._poll(request_time)
599 if ready:
599 if ready:
600 self._beating = True
600 self._beating = True
601 # the poll above guarantees we have something to recv
601 # the poll above guarantees we have something to recv
602 self.socket.recv()
602 self.socket.recv()
603 # sleep the remainder of the cycle
603 # sleep the remainder of the cycle
604 remainder = self.time_to_dead - (time.time() - request_time)
604 remainder = self.time_to_dead - (time.time() - request_time)
605 if remainder > 0:
605 if remainder > 0:
606 time.sleep(remainder)
606 time.sleep(remainder)
607 continue
607 continue
608 else:
608 else:
609 # nothing was received within the time limit, signal heart failure
609 # nothing was received within the time limit, signal heart failure
610 self._beating = False
610 self._beating = False
611 since_last_heartbeat = time.time() - request_time
611 since_last_heartbeat = time.time() - request_time
612 self.call_handlers(since_last_heartbeat)
612 self.call_handlers(since_last_heartbeat)
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 self._create_socket()
614 self._create_socket()
615 continue
615 continue
616 try:
616 try:
617 self.socket.close()
617 self.socket.close()
618 except:
618 except:
619 pass
619 pass
620
620
621 def pause(self):
621 def pause(self):
622 """Pause the heartbeat."""
622 """Pause the heartbeat."""
623 self._pause = True
623 self._pause = True
624
624
625 def unpause(self):
625 def unpause(self):
626 """Unpause the heartbeat."""
626 """Unpause the heartbeat."""
627 self._pause = False
627 self._pause = False
628
628
629 def is_beating(self):
629 def is_beating(self):
630 """Is the heartbeat running and responsive (and not paused)."""
630 """Is the heartbeat running and responsive (and not paused)."""
631 if self.is_alive() and not self._pause and self._beating:
631 if self.is_alive() and not self._pause and self._beating:
632 return True
632 return True
633 else:
633 else:
634 return False
634 return False
635
635
636 def stop(self):
636 def stop(self):
637 """Stop the channel's event loop and join its thread."""
637 """Stop the channel's event loop and join its thread."""
638 self._running = False
638 self._running = False
639 super(HBChannel, self).stop()
639 super(HBChannel, self).stop()
640
640
641 def call_handlers(self, since_last_heartbeat):
641 def call_handlers(self, since_last_heartbeat):
642 """This method is called in the ioloop thread when a message arrives.
642 """This method is called in the ioloop thread when a message arrives.
643
643
644 Subclasses should override this method to handle incoming messages.
644 Subclasses should override this method to handle incoming messages.
645 It is important to remember that this method is called in the thread
645 It is important to remember that this method is called in the thread
646 so that some logic must be done to ensure that the application level
646 so that some logic must be done to ensure that the application level
647 handlers are called in the application thread.
647 handlers are called in the application thread.
648 """
648 """
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650
650
651
651
652 #-----------------------------------------------------------------------------
652 #-----------------------------------------------------------------------------
653 # Main kernel manager class
653 # Main kernel manager class
654 #-----------------------------------------------------------------------------
654 #-----------------------------------------------------------------------------
655
655
656 class KernelManager(Configurable):
656 class KernelManager(Configurable):
657 """Manages a single kernel on this host along with its channels.
657 """Manages a single kernel on this host along with its channels.
658
658
659 There are four channels associated with each kernel:
659 There are four channels associated with each kernel:
660
660
661 * shell: for request/reply calls to the kernel.
661 * shell: for request/reply calls to the kernel.
662 * iopub: for the kernel to publish results to frontends.
662 * iopub: for the kernel to publish results to frontends.
663 * hb: for monitoring the kernel's heartbeat.
663 * hb: for monitoring the kernel's heartbeat.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
665
665
666 The usage of the channels that this class manages is optional. It is
666 The usage of the channels that this class manages is optional. It is
667 entirely possible to connect to the kernels directly using ZeroMQ
667 entirely possible to connect to the kernels directly using ZeroMQ
668 sockets. These channels are useful primarily for talking to a kernel
668 sockets. These channels are useful primarily for talking to a kernel
669 whose :class:`KernelManager` is in the same process.
669 whose :class:`KernelManager` is in the same process.
670
670
671 This version manages kernels started using Popen.
671 This version manages kernels started using Popen.
672 """
672 """
673 # The PyZMQ Context to use for communication with the kernel.
673 # The PyZMQ Context to use for communication with the kernel.
674 context = Instance(zmq.Context)
674 context = Instance(zmq.Context)
675 def _context_default(self):
675 def _context_default(self):
676 return zmq.Context.instance()
676 return zmq.Context.instance()
677
677
678 # The Session to use for communication with the kernel.
678 # The Session to use for communication with the kernel.
679 session = Instance(Session)
679 session = Instance(Session)
680 def _session_default(self):
680 def _session_default(self):
681 return Session(config=self.config)
681 return Session(config=self.config)
682
682
683 # The kernel process with which the KernelManager is communicating.
683 # The kernel process with which the KernelManager is communicating.
684 # generally a Popen instance
684 # generally a Popen instance
685 kernel = Any()
685 kernel = Any()
686
686
687 kernel_cmd = List(Unicode, config=True,
687 kernel_cmd = List(Unicode, config=True,
688 help="""The Popen Command to launch the kernel.
688 help="""The Popen Command to launch the kernel.
689 Override this if you have a custom
689 Override this if you have a custom
690 """
690 """
691 )
691 )
692 def _kernel_cmd_changed(self, name, old, new):
692 def _kernel_cmd_changed(self, name, old, new):
693 print 'kernel cmd changed', new
693 print 'kernel cmd changed', new
694 self.ipython_kernel = False
694 self.ipython_kernel = False
695
695
696 ipython_kernel = Bool(True)
696 ipython_kernel = Bool(True)
697
697
698
698
699 # The addresses for the communication channels.
699 # The addresses for the communication channels.
700 connection_file = Unicode('')
700 connection_file = Unicode('')
701
701
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
703
703
704 ip = Unicode(LOCALHOST, config=True,
704 ip = Unicode(LOCALHOST, config=True,
705 help="""Set the kernel\'s IP address [default localhost].
705 help="""Set the kernel\'s IP address [default localhost].
706 If the IP address is something other than localhost, then
706 If the IP address is something other than localhost, then
707 Consoles on other machines will be able to connect
707 Consoles on other machines will be able to connect
708 to the Kernel, so be careful!"""
708 to the Kernel, so be careful!"""
709 )
709 )
710 def _ip_default(self):
710 def _ip_default(self):
711 if self.transport == 'ipc':
711 if self.transport == 'ipc':
712 if self.connection_file:
712 if self.connection_file:
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 else:
714 else:
715 return 'kernel-ipc'
715 return 'kernel-ipc'
716 else:
716 else:
717 return LOCALHOST
717 return LOCALHOST
718 def _ip_changed(self, name, old, new):
718 def _ip_changed(self, name, old, new):
719 if new == '*':
719 if new == '*':
720 self.ip = '0.0.0.0'
720 self.ip = '0.0.0.0'
721 shell_port = Integer(0)
721 shell_port = Integer(0)
722 iopub_port = Integer(0)
722 iopub_port = Integer(0)
723 stdin_port = Integer(0)
723 stdin_port = Integer(0)
724 hb_port = Integer(0)
724 hb_port = Integer(0)
725
725
726 # The classes to use for the various channels.
726 # The classes to use for the various channels.
727 shell_channel_class = Type(ShellChannel)
727 shell_channel_class = Type(ShellChannel)
728 iopub_channel_class = Type(IOPubChannel)
728 iopub_channel_class = Type(IOPubChannel)
729 stdin_channel_class = Type(StdInChannel)
729 stdin_channel_class = Type(StdInChannel)
730 hb_channel_class = Type(HBChannel)
730 hb_channel_class = Type(HBChannel)
731
731
732 # Protected traits.
732 # Protected traits.
733 _launch_args = Any
733 _launch_args = Any
734 _shell_channel = Any
734 _shell_channel = Any
735 _iopub_channel = Any
735 _iopub_channel = Any
736 _stdin_channel = Any
736 _stdin_channel = Any
737 _hb_channel = Any
737 _hb_channel = Any
738 _connection_file_written=Bool(False)
738 _connection_file_written=Bool(False)
739
739
740 def __del__(self):
740 def __del__(self):
741 self.cleanup_connection_file()
741 self.cleanup_connection_file()
742
742
743 #--------------------------------------------------------------------------
743 #--------------------------------------------------------------------------
744 # Channel management methods:
744 # Channel management methods:
745 #--------------------------------------------------------------------------
745 #--------------------------------------------------------------------------
746
746
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 """Starts the channels for this kernel.
748 """Starts the channels for this kernel.
749
749
750 This will create the channels if they do not exist and then start
750 This will create the channels if they do not exist and then start
751 them (their activity runs in a thread). If port numbers of 0 are
751 them (their activity runs in a thread). If port numbers of 0 are
752 being used (random ports) then you must first call
752 being used (random ports) then you must first call
753 :method:`start_kernel`. If the channels have been stopped and you
753 :method:`start_kernel`. If the channels have been stopped and you
754 call this, :class:`RuntimeError` will be raised.
754 call this, :class:`RuntimeError` will be raised.
755 """
755 """
756 if shell:
756 if shell:
757 self.shell_channel.start()
757 self.shell_channel.start()
758 if iopub:
758 if iopub:
759 self.iopub_channel.start()
759 self.iopub_channel.start()
760 if stdin:
760 if stdin:
761 self.stdin_channel.start()
761 self.stdin_channel.start()
762 self.shell_channel.allow_stdin = True
762 self.shell_channel.allow_stdin = True
763 else:
763 else:
764 self.shell_channel.allow_stdin = False
764 self.shell_channel.allow_stdin = False
765 if hb:
765 if hb:
766 self.hb_channel.start()
766 self.hb_channel.start()
767
767
768 def stop_channels(self):
768 def stop_channels(self):
769 """Stops all the running channels for this kernel.
769 """Stops all the running channels for this kernel.
770
770
771 This stops their event loops and joins their threads.
771 This stops their event loops and joins their threads.
772 """
772 """
773 if self.shell_channel.is_alive():
773 if self.shell_channel.is_alive():
774 self.shell_channel.stop()
774 self.shell_channel.stop()
775 if self.iopub_channel.is_alive():
775 if self.iopub_channel.is_alive():
776 self.iopub_channel.stop()
776 self.iopub_channel.stop()
777 if self.stdin_channel.is_alive():
777 if self.stdin_channel.is_alive():
778 self.stdin_channel.stop()
778 self.stdin_channel.stop()
779 if self.hb_channel.is_alive():
779 if self.hb_channel.is_alive():
780 self.hb_channel.stop()
780 self.hb_channel.stop()
781
781
782 @property
782 @property
783 def channels_running(self):
783 def channels_running(self):
784 """Are any of the channels created and running?"""
784 """Are any of the channels created and running?"""
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787
787
788 def _make_url(self, port):
788 def _make_url(self, port):
789 """Make a zmq url with a port.
789 """Make a zmq url with a port.
790
790
791 There are two cases that this handles:
791 There are two cases that this handles:
792
792
793 * tcp: tcp://ip:port
793 * tcp: tcp://ip:port
794 * ipc: ipc://ip-port
794 * ipc: ipc://ip-port
795 """
795 """
796 if self.transport == 'tcp':
796 if self.transport == 'tcp':
797 return "tcp://%s:%i" % (self.ip, port)
797 return "tcp://%s:%i" % (self.ip, port)
798 else:
798 else:
799 return "%s://%s-%s" % (self.transport, self.ip, port)
799 return "%s://%s-%s" % (self.transport, self.ip, port)
800
800
801 @property
801 @property
802 def shell_channel(self):
802 def shell_channel(self):
803 """Get the shell channel object for this kernel."""
803 """Get the shell channel object for this kernel."""
804 if self._shell_channel is None:
804 if self._shell_channel is None:
805 self._shell_channel = self.shell_channel_class(
805 self._shell_channel = self.shell_channel_class(
806 self.context, self.session, self._make_url(self.shell_port)
806 self.context, self.session, self._make_url(self.shell_port)
807 )
807 )
808 return self._shell_channel
808 return self._shell_channel
809
809
810 @property
810 @property
811 def iopub_channel(self):
811 def iopub_channel(self):
812 """Get the iopub channel object for this kernel."""
812 """Get the iopub channel object for this kernel."""
813 if self._iopub_channel is None:
813 if self._iopub_channel is None:
814 self._iopub_channel = self.iopub_channel_class(
814 self._iopub_channel = self.iopub_channel_class(
815 self.context, self.session, self._make_url(self.iopub_port)
815 self.context, self.session, self._make_url(self.iopub_port)
816 )
816 )
817 return self._iopub_channel
817 return self._iopub_channel
818
818
819 @property
819 @property
820 def stdin_channel(self):
820 def stdin_channel(self):
821 """Get the stdin channel object for this kernel."""
821 """Get the stdin channel object for this kernel."""
822 if self._stdin_channel is None:
822 if self._stdin_channel is None:
823 self._stdin_channel = self.stdin_channel_class(
823 self._stdin_channel = self.stdin_channel_class(
824 self.context, self.session, self._make_url(self.stdin_port)
824 self.context, self.session, self._make_url(self.stdin_port)
825 )
825 )
826 return self._stdin_channel
826 return self._stdin_channel
827
827
828 @property
828 @property
829 def hb_channel(self):
829 def hb_channel(self):
830 """Get the hb channel object for this kernel."""
830 """Get the hb channel object for this kernel."""
831 if self._hb_channel is None:
831 if self._hb_channel is None:
832 self._hb_channel = self.hb_channel_class(
832 self._hb_channel = self.hb_channel_class(
833 self.context, self.session, self._make_url(self.hb_port)
833 self.context, self.session, self._make_url(self.hb_port)
834 )
834 )
835 return self._hb_channel
835 return self._hb_channel
836
836
837 #--------------------------------------------------------------------------
837 #--------------------------------------------------------------------------
838 # Connection and ipc file management
838 # Connection and ipc file management
839 #--------------------------------------------------------------------------
839 #--------------------------------------------------------------------------
840
840
841 def cleanup_connection_file(self):
841 def cleanup_connection_file(self):
842 """Cleanup connection file *if we wrote it*
842 """Cleanup connection file *if we wrote it*
843
843
844 Will not raise if the connection file was already removed somehow.
844 Will not raise if the connection file was already removed somehow.
845 """
845 """
846 if self._connection_file_written:
846 if self._connection_file_written:
847 # cleanup connection files on full shutdown of kernel we started
847 # cleanup connection files on full shutdown of kernel we started
848 self._connection_file_written = False
848 self._connection_file_written = False
849 try:
849 try:
850 os.remove(self.connection_file)
850 os.remove(self.connection_file)
851 except (IOError, OSError):
851 except (IOError, OSError):
852 pass
852 pass
853
853
854 def cleanup_ipc_files(self):
854 def cleanup_ipc_files(self):
855 """Cleanup ipc files if we wrote them."""
855 """Cleanup ipc files if we wrote them."""
856 if self.transport != 'ipc':
856 if self.transport != 'ipc':
857 return
857 return
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 ipcfile = "%s-%i" % (self.ip, port)
859 ipcfile = "%s-%i" % (self.ip, port)
860 try:
860 try:
861 os.remove(ipcfile)
861 os.remove(ipcfile)
862 except (IOError, OSError):
862 except (IOError, OSError):
863 pass
863 pass
864
864
865 def load_connection_file(self):
865 def load_connection_file(self):
866 """Load connection info from JSON dict in self.connection_file."""
866 """Load connection info from JSON dict in self.connection_file."""
867 with open(self.connection_file) as f:
867 with open(self.connection_file) as f:
868 cfg = json.loads(f.read())
868 cfg = json.loads(f.read())
869
869
870 from pprint import pprint
870 from pprint import pprint
871 pprint(cfg)
871 pprint(cfg)
872 self.transport = cfg.get('transport', 'tcp')
872 self.transport = cfg.get('transport', 'tcp')
873 self.ip = cfg['ip']
873 self.ip = cfg['ip']
874 self.shell_port = cfg['shell_port']
874 self.shell_port = cfg['shell_port']
875 self.stdin_port = cfg['stdin_port']
875 self.stdin_port = cfg['stdin_port']
876 self.iopub_port = cfg['iopub_port']
876 self.iopub_port = cfg['iopub_port']
877 self.hb_port = cfg['hb_port']
877 self.hb_port = cfg['hb_port']
878 self.session.key = str_to_bytes(cfg['key'])
878 self.session.key = str_to_bytes(cfg['key'])
879
879
880 def write_connection_file(self):
880 def write_connection_file(self):
881 """Write connection info to JSON dict in self.connection_file."""
881 """Write connection info to JSON dict in self.connection_file."""
882 if self._connection_file_written:
882 if self._connection_file_written:
883 return
883 return
884 self.connection_file,cfg = write_connection_file(self.connection_file,
884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 transport=self.transport, ip=self.ip, key=self.session.key,
885 transport=self.transport, ip=self.ip, key=self.session.key,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 shell_port=self.shell_port, hb_port=self.hb_port)
887 shell_port=self.shell_port, hb_port=self.hb_port)
888 # write_connection_file also sets default ports:
888 # write_connection_file also sets default ports:
889 self.shell_port = cfg['shell_port']
889 self.shell_port = cfg['shell_port']
890 self.stdin_port = cfg['stdin_port']
890 self.stdin_port = cfg['stdin_port']
891 self.iopub_port = cfg['iopub_port']
891 self.iopub_port = cfg['iopub_port']
892 self.hb_port = cfg['hb_port']
892 self.hb_port = cfg['hb_port']
893
893
894 self._connection_file_written = True
894 self._connection_file_written = True
895
895
896 #--------------------------------------------------------------------------
896 #--------------------------------------------------------------------------
897 # Kernel management
897 # Kernel management
898 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
899
899
900 def format_kernel_cmd(self, **kw):
900 def format_kernel_cmd(self, **kw):
901 """format templated args (e.g. {connection_file})"""
901 """format templated args (e.g. {connection_file})"""
902 if self.kernel_cmd:
902 if self.kernel_cmd:
903 cmd = self.kernel_cmd
903 cmd = self.kernel_cmd
904 else:
904 else:
905 cmd = make_ipkernel_cmd(
905 cmd = make_ipkernel_cmd(
906 'from IPython.zmq.ipkernel import main; main()',
906 'from IPython.zmq.ipkernel import main; main()',
907 **kw
907 **kw
908 )
908 )
909 ns = dict(connection_file=self.connection_file)
909 ns = dict(connection_file=self.connection_file)
910 ns.update(self._launch_args)
910 ns.update(self._launch_args)
911 return [ c.format(**ns) for c in cmd ]
911 return [ c.format(**ns) for c in cmd ]
912
912
913 def _launch_kernel(self, kernel_cmd, **kw):
913 def _launch_kernel(self, kernel_cmd, **kw):
914 """actually launch the kernel
914 """actually launch the kernel
915
915
916 override in a subclass to launch kernel subprocesses differently
916 override in a subclass to launch kernel subprocesses differently
917 """
917 """
918 return launch_kernel(kernel_cmd, **kw)
918 return launch_kernel(kernel_cmd, **kw)
919
919
920 def start_kernel(self, **kw):
920 def start_kernel(self, **kw):
921 """Starts a kernel on this host in a separate process.
921 """Starts a kernel on this host in a separate process.
922
922
923 If random ports (port=0) are being used, this method must be called
923 If random ports (port=0) are being used, this method must be called
924 before the channels are created.
924 before the channels are created.
925
925
926 Parameters:
926 Parameters:
927 -----------
927 -----------
928 launcher : callable, optional (default None)
929 A custom function for launching the kernel process (generally a
930 wrapper around ``entry_point.base_launch_kernel``). In most cases,
931 it should not be necessary to use this parameter.
932
933 **kw : optional
928 **kw : optional
934 keyword arguments that are passed down into the launcher
929 keyword arguments that are passed down to build the kernel_cmd
935 callable.
930 and launching the kernel (e.g. Popen kwargs).
936 """
931 """
937 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
938 raise RuntimeError("Can only launch a kernel on a local interface. "
933 raise RuntimeError("Can only launch a kernel on a local interface. "
939 "Make sure that the '*_address' attributes are "
934 "Make sure that the '*_address' attributes are "
940 "configured properly. "
935 "configured properly. "
941 "Currently valid addresses are: %s"%LOCAL_IPS
936 "Currently valid addresses are: %s"%LOCAL_IPS
942 )
937 )
943
938
944 # write connection file / get default ports
939 # write connection file / get default ports
945 self.write_connection_file()
940 self.write_connection_file()
946
941
947 # save kwargs for use in restart
942 # save kwargs for use in restart
948 self._launch_args = kw.copy()
943 self._launch_args = kw.copy()
949 # build the Popen cmd
944 # build the Popen cmd
950 kernel_cmd = self.format_kernel_cmd(**kw)
945 kernel_cmd = self.format_kernel_cmd(**kw)
951 # launch the kernel subprocess
946 # launch the kernel subprocess
952 self.kernel = self._launch_kernel(kernel_cmd,
947 self.kernel = self._launch_kernel(kernel_cmd,
953 ipython_kernel=self.ipython_kernel,
948 ipython_kernel=self.ipython_kernel,
954 **kw)
949 **kw)
955
950
956 def shutdown_kernel(self, now=False, restart=False):
951 def shutdown_kernel(self, now=False, restart=False):
957 """Attempts to the stop the kernel process cleanly.
952 """Attempts to the stop the kernel process cleanly.
958
953
959 This attempts to shutdown the kernels cleanly by:
954 This attempts to shutdown the kernels cleanly by:
960
955
961 1. Sending it a shutdown message over the shell channel.
956 1. Sending it a shutdown message over the shell channel.
962 2. If that fails, the kernel is shutdown forcibly by sending it
957 2. If that fails, the kernel is shutdown forcibly by sending it
963 a signal.
958 a signal.
964
959
965 Parameters:
960 Parameters:
966 -----------
961 -----------
967 now : bool
962 now : bool
968 Should the kernel be forcible killed *now*. This skips the
963 Should the kernel be forcible killed *now*. This skips the
969 first, nice shutdown attempt.
964 first, nice shutdown attempt.
970 restart: bool
965 restart: bool
971 Will this kernel be restarted after it is shutdown. When this
966 Will this kernel be restarted after it is shutdown. When this
972 is True, connection files will not be cleaned up.
967 is True, connection files will not be cleaned up.
973 """
968 """
974 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
975 if sys.platform == 'win32':
970 if sys.platform == 'win32':
976 self._kill_kernel()
971 self._kill_kernel()
977 return
972 return
978
973
979 # Pause the heart beat channel if it exists.
974 # Pause the heart beat channel if it exists.
980 if self._hb_channel is not None:
975 if self._hb_channel is not None:
981 self._hb_channel.pause()
976 self._hb_channel.pause()
982
977
983 if now:
978 if now:
984 if self.has_kernel:
979 if self.has_kernel:
985 self._kill_kernel()
980 self._kill_kernel()
986 else:
981 else:
987 # Don't send any additional kernel kill messages immediately, to give
982 # Don't send any additional kernel kill messages immediately, to give
988 # the kernel a chance to properly execute shutdown actions. Wait for at
983 # the kernel a chance to properly execute shutdown actions. Wait for at
989 # most 1s, checking every 0.1s.
984 # most 1s, checking every 0.1s.
990 self.shell_channel.shutdown(restart=restart)
985 self.shell_channel.shutdown(restart=restart)
991 for i in range(10):
986 for i in range(10):
992 if self.is_alive:
987 if self.is_alive:
993 time.sleep(0.1)
988 time.sleep(0.1)
994 else:
989 else:
995 break
990 break
996 else:
991 else:
997 # OK, we've waited long enough.
992 # OK, we've waited long enough.
998 if self.has_kernel:
993 if self.has_kernel:
999 self._kill_kernel()
994 self._kill_kernel()
1000
995
1001 if not restart:
996 if not restart:
1002 self.cleanup_connection_file()
997 self.cleanup_connection_file()
1003 self.cleanup_ipc_files()
998 self.cleanup_ipc_files()
1004 else:
999 else:
1005 self.cleanup_ipc_files()
1000 self.cleanup_ipc_files()
1006
1001
1007 def restart_kernel(self, now=False, **kw):
1002 def restart_kernel(self, now=False, **kw):
1008 """Restarts a kernel with the arguments that were used to launch it.
1003 """Restarts a kernel with the arguments that were used to launch it.
1009
1004
1010 If the old kernel was launched with random ports, the same ports will be
1005 If the old kernel was launched with random ports, the same ports will be
1011 used for the new kernel. The same connection file is used again.
1006 used for the new kernel. The same connection file is used again.
1012
1007
1013 Parameters
1008 Parameters
1014 ----------
1009 ----------
1015 now : bool, optional
1010 now : bool, optional
1016 If True, the kernel is forcefully restarted *immediately*, without
1011 If True, the kernel is forcefully restarted *immediately*, without
1017 having a chance to do any cleanup action. Otherwise the kernel is
1012 having a chance to do any cleanup action. Otherwise the kernel is
1018 given 1s to clean up before a forceful restart is issued.
1013 given 1s to clean up before a forceful restart is issued.
1019
1014
1020 In all cases the kernel is restarted, the only difference is whether
1015 In all cases the kernel is restarted, the only difference is whether
1021 it is given a chance to perform a clean shutdown or not.
1016 it is given a chance to perform a clean shutdown or not.
1022
1017
1023 **kw : optional
1018 **kw : optional
1024 Any options specified here will overwrite those used to launch the
1019 Any options specified here will overwrite those used to launch the
1025 kernel.
1020 kernel.
1026 """
1021 """
1027 if self._launch_args is None:
1022 if self._launch_args is None:
1028 raise RuntimeError("Cannot restart the kernel. "
1023 raise RuntimeError("Cannot restart the kernel. "
1029 "No previous call to 'start_kernel'.")
1024 "No previous call to 'start_kernel'.")
1030 else:
1025 else:
1031 # Stop currently running kernel.
1026 # Stop currently running kernel.
1032 self.shutdown_kernel(now=now, restart=True)
1027 self.shutdown_kernel(now=now, restart=True)
1033
1028
1034 # Start new kernel.
1029 # Start new kernel.
1035 self._launch_args.update(kw)
1030 self._launch_args.update(kw)
1036 self.start_kernel(**self._launch_args)
1031 self.start_kernel(**self._launch_args)
1037
1032
1038 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1039 # unless there is some delay here.
1034 # unless there is some delay here.
1040 if sys.platform == 'win32':
1035 if sys.platform == 'win32':
1041 time.sleep(0.2)
1036 time.sleep(0.2)
1042
1037
1043 @property
1038 @property
1044 def has_kernel(self):
1039 def has_kernel(self):
1045 """Has a kernel been started that we are managing."""
1040 """Has a kernel been started that we are managing."""
1046 return self.kernel is not None
1041 return self.kernel is not None
1047
1042
1048 def _kill_kernel(self):
1043 def _kill_kernel(self):
1049 """Kill the running kernel.
1044 """Kill the running kernel.
1050
1045
1051 This is a private method, callers should use shutdown_kernel(now=True).
1046 This is a private method, callers should use shutdown_kernel(now=True).
1052 """
1047 """
1053 if self.has_kernel:
1048 if self.has_kernel:
1054 # Pause the heart beat channel if it exists.
1049 # Pause the heart beat channel if it exists.
1055 if self._hb_channel is not None:
1050 if self._hb_channel is not None:
1056 self._hb_channel.pause()
1051 self._hb_channel.pause()
1057
1052
1058 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1059 # TerminateProcess() on Win32).
1054 # TerminateProcess() on Win32).
1060 try:
1055 try:
1061 self.kernel.kill()
1056 self.kernel.kill()
1062 except OSError as e:
1057 except OSError as e:
1063 # In Windows, we will get an Access Denied error if the process
1058 # In Windows, we will get an Access Denied error if the process
1064 # has already terminated. Ignore it.
1059 # has already terminated. Ignore it.
1065 if sys.platform == 'win32':
1060 if sys.platform == 'win32':
1066 if e.winerror != 5:
1061 if e.winerror != 5:
1067 raise
1062 raise
1068 # On Unix, we may get an ESRCH error if the process has already
1063 # On Unix, we may get an ESRCH error if the process has already
1069 # terminated. Ignore it.
1064 # terminated. Ignore it.
1070 else:
1065 else:
1071 from errno import ESRCH
1066 from errno import ESRCH
1072 if e.errno != ESRCH:
1067 if e.errno != ESRCH:
1073 raise
1068 raise
1074
1069
1075 # Block until the kernel terminates.
1070 # Block until the kernel terminates.
1076 self.kernel.wait()
1071 self.kernel.wait()
1077 self.kernel = None
1072 self.kernel = None
1078 else:
1073 else:
1079 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1080
1075
1081 def interrupt_kernel(self):
1076 def interrupt_kernel(self):
1082 """Interrupts the kernel by sending it a signal.
1077 """Interrupts the kernel by sending it a signal.
1083
1078
1084 Unlike ``signal_kernel``, this operation is well supported on all
1079 Unlike ``signal_kernel``, this operation is well supported on all
1085 platforms.
1080 platforms.
1086 """
1081 """
1087 if self.has_kernel:
1082 if self.has_kernel:
1088 if sys.platform == 'win32':
1083 if sys.platform == 'win32':
1089 from parentpoller import ParentPollerWindows as Poller
1084 from parentpoller import ParentPollerWindows as Poller
1090 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1091 else:
1086 else:
1092 self.kernel.send_signal(signal.SIGINT)
1087 self.kernel.send_signal(signal.SIGINT)
1093 else:
1088 else:
1094 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1095
1090
1096 def signal_kernel(self, signum):
1091 def signal_kernel(self, signum):
1097 """Sends a signal to the kernel.
1092 """Sends a signal to the kernel.
1098
1093
1099 Note that since only SIGTERM is supported on Windows, this function is
1094 Note that since only SIGTERM is supported on Windows, this function is
1100 only useful on Unix systems.
1095 only useful on Unix systems.
1101 """
1096 """
1102 if self.has_kernel:
1097 if self.has_kernel:
1103 self.kernel.send_signal(signum)
1098 self.kernel.send_signal(signum)
1104 else:
1099 else:
1105 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1106
1101
1107 @property
1102 @property
1108 def is_alive(self):
1103 def is_alive(self):
1109 """Is the kernel process still running?"""
1104 """Is the kernel process still running?"""
1110 if self.has_kernel:
1105 if self.has_kernel:
1111 if self.kernel.poll() is None:
1106 if self.kernel.poll() is None:
1112 return True
1107 return True
1113 else:
1108 else:
1114 return False
1109 return False
1115 elif self._hb_channel is not None:
1110 elif self._hb_channel is not None:
1116 # We didn't start the kernel with this KernelManager so we
1111 # We didn't start the kernel with this KernelManager so we
1117 # use the heartbeat.
1112 # use the heartbeat.
1118 return self._hb_channel.is_beating()
1113 return self._hb_channel.is_beating()
1119 else:
1114 else:
1120 # no heartbeat and not local, we can't tell if it's running,
1115 # no heartbeat and not local, we can't tell if it's running,
1121 # so naively return True
1116 # so naively return True
1122 return True
1117 return True
1123
1118
1124
1119
1125 #-----------------------------------------------------------------------------
1120 #-----------------------------------------------------------------------------
1126 # ABC Registration
1121 # ABC Registration
1127 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1128
1123
1129 ShellChannelABC.register(ShellChannel)
1124 ShellChannelABC.register(ShellChannel)
1130 IOPubChannelABC.register(IOPubChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1131 HBChannelABC.register(HBChannel)
1126 HBChannelABC.register(HBChannel)
1132 StdInChannelABC.register(StdInChannel)
1127 StdInChannelABC.register(StdInChannel)
1133 KernelManagerABC.register(KernelManager)
1128 KernelManagerABC.register(KernelManager)
1134
1129
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now