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