##// END OF EJS Templates
add control channel...
MinRK -
Show More
@@ -1,452 +1,478 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
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) 2013 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 socket
24 import sys
24 import sys
25 from getpass import getpass
25 from getpass import getpass
26 from subprocess import Popen, PIPE
26 from subprocess import Popen, PIPE
27 import tempfile
27 import tempfile
28
28
29 # external imports
29 # external imports
30 from IPython.external.ssh import tunnel
30 from IPython.external.ssh import tunnel
31
31
32 # IPython imports
32 # IPython imports
33 # from IPython.config import Configurable
33 # from IPython.config import Configurable
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.utils.localinterfaces import LOCALHOST
35 from IPython.utils.localinterfaces import LOCALHOST
36 from IPython.utils.path import filefind, get_ipython_dir
36 from IPython.utils.path import filefind, get_ipython_dir
37 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
37 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
38 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
39 Bool, Integer, Unicode, CaselessStrEnum,
39 Bool, Integer, Unicode, CaselessStrEnum,
40 HasTraits,
40 HasTraits,
41 )
41 )
42
42
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Working with Connection Files
45 # Working with Connection Files
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
48 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
49 ip=LOCALHOST, key=b'', transport='tcp'):
49 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
50 """Generates a JSON config file, including the selection of random ports.
50 """Generates a JSON config file, including the selection of random ports.
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54
54
55 fname : unicode
55 fname : unicode
56 The path to the file to write
56 The path to the file to write
57
57
58 shell_port : int, optional
58 shell_port : int, optional
59 The port to use for ROUTER channel.
59 The port to use for ROUTER channel.
60
60
61 iopub_port : int, optional
61 iopub_port : int, optional
62 The port to use for the SUB channel.
62 The port to use for the SUB channel.
63
63
64 stdin_port : int, optional
64 stdin_port : int, optional
65 The port to use for the REQ (raw input) channel.
65 The port to use for the ROUTER (raw input) channel.
66
67 control_port : int, optional
68 The port to use for the ROUTER (raw input) channel.
66
69
67 hb_port : int, optional
70 hb_port : int, optional
68 The port to use for the hearbeat REP channel.
71 The port to use for the hearbeat REP channel.
69
72
70 ip : str, optional
73 ip : str, optional
71 The ip address the kernel will bind to.
74 The ip address the kernel will bind to.
72
75
73 key : str, optional
76 key : str, optional
74 The Session key used for HMAC authentication.
77 The Session key used for HMAC authentication.
75
78
76 """
79 """
77 # default to temporary connector file
80 # default to temporary connector file
78 if not fname:
81 if not fname:
79 fname = tempfile.mktemp('.json')
82 fname = tempfile.mktemp('.json')
80
83
81 # Find open ports as necessary.
84 # Find open ports as necessary.
82
85
83 ports = []
86 ports = []
84 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
87 ports_needed = int(shell_port <= 0) + \
85 int(stdin_port <= 0) + int(hb_port <= 0)
88 int(iopub_port <= 0) + \
89 int(stdin_port <= 0) + \
90 int(control_port <= 0) + \
91 int(hb_port <= 0)
86 if transport == 'tcp':
92 if transport == 'tcp':
87 for i in range(ports_needed):
93 for i in range(ports_needed):
88 sock = socket.socket()
94 sock = socket.socket()
89 sock.bind(('', 0))
95 sock.bind(('', 0))
90 ports.append(sock)
96 ports.append(sock)
91 for i, sock in enumerate(ports):
97 for i, sock in enumerate(ports):
92 port = sock.getsockname()[1]
98 port = sock.getsockname()[1]
93 sock.close()
99 sock.close()
94 ports[i] = port
100 ports[i] = port
95 else:
101 else:
96 N = 1
102 N = 1
97 for i in range(ports_needed):
103 for i in range(ports_needed):
98 while os.path.exists("%s-%s" % (ip, str(N))):
104 while os.path.exists("%s-%s" % (ip, str(N))):
99 N += 1
105 N += 1
100 ports.append(N)
106 ports.append(N)
101 N += 1
107 N += 1
102 if shell_port <= 0:
108 if shell_port <= 0:
103 shell_port = ports.pop(0)
109 shell_port = ports.pop(0)
104 if iopub_port <= 0:
110 if iopub_port <= 0:
105 iopub_port = ports.pop(0)
111 iopub_port = ports.pop(0)
106 if stdin_port <= 0:
112 if stdin_port <= 0:
107 stdin_port = ports.pop(0)
113 stdin_port = ports.pop(0)
114 if control_port <= 0:
115 control_port = ports.pop(0)
108 if hb_port <= 0:
116 if hb_port <= 0:
109 hb_port = ports.pop(0)
117 hb_port = ports.pop(0)
110
118
111 cfg = dict( shell_port=shell_port,
119 cfg = dict( shell_port=shell_port,
112 iopub_port=iopub_port,
120 iopub_port=iopub_port,
113 stdin_port=stdin_port,
121 stdin_port=stdin_port,
122 control_port=control_port,
114 hb_port=hb_port,
123 hb_port=hb_port,
115 )
124 )
116 cfg['ip'] = ip
125 cfg['ip'] = ip
117 cfg['key'] = bytes_to_str(key)
126 cfg['key'] = bytes_to_str(key)
118 cfg['transport'] = transport
127 cfg['transport'] = transport
119
128
120 with open(fname, 'w') as f:
129 with open(fname, 'w') as f:
121 f.write(json.dumps(cfg, indent=2))
130 f.write(json.dumps(cfg, indent=2))
122
131
123 return fname, cfg
132 return fname, cfg
124
133
125
134
126 def get_connection_file(app=None):
135 def get_connection_file(app=None):
127 """Return the path to the connection file of an app
136 """Return the path to the connection file of an app
128
137
129 Parameters
138 Parameters
130 ----------
139 ----------
131 app : IPKernelApp instance [optional]
140 app : IPKernelApp instance [optional]
132 If unspecified, the currently running app will be used
141 If unspecified, the currently running app will be used
133 """
142 """
134 if app is None:
143 if app is None:
135 from IPython.kernel.zmq.kernelapp import IPKernelApp
144 from IPython.kernel.zmq.kernelapp import IPKernelApp
136 if not IPKernelApp.initialized():
145 if not IPKernelApp.initialized():
137 raise RuntimeError("app not specified, and not in a running Kernel")
146 raise RuntimeError("app not specified, and not in a running Kernel")
138
147
139 app = IPKernelApp.instance()
148 app = IPKernelApp.instance()
140 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
149 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
141
150
142
151
143 def find_connection_file(filename, profile=None):
152 def find_connection_file(filename, profile=None):
144 """find a connection file, and return its absolute path.
153 """find a connection file, and return its absolute path.
145
154
146 The current working directory and the profile's security
155 The current working directory and the profile's security
147 directory will be searched for the file if it is not given by
156 directory will be searched for the file if it is not given by
148 absolute path.
157 absolute path.
149
158
150 If profile is unspecified, then the current running application's
159 If profile is unspecified, then the current running application's
151 profile will be used, or 'default', if not run from IPython.
160 profile will be used, or 'default', if not run from IPython.
152
161
153 If the argument does not match an existing file, it will be interpreted as a
162 If the argument does not match an existing file, it will be interpreted as a
154 fileglob, and the matching file in the profile's security dir with
163 fileglob, and the matching file in the profile's security dir with
155 the latest access time will be used.
164 the latest access time will be used.
156
165
157 Parameters
166 Parameters
158 ----------
167 ----------
159 filename : str
168 filename : str
160 The connection file or fileglob to search for.
169 The connection file or fileglob to search for.
161 profile : str [optional]
170 profile : str [optional]
162 The name of the profile to use when searching for the connection file,
171 The name of the profile to use when searching for the connection file,
163 if different from the current IPython session or 'default'.
172 if different from the current IPython session or 'default'.
164
173
165 Returns
174 Returns
166 -------
175 -------
167 str : The absolute path of the connection file.
176 str : The absolute path of the connection file.
168 """
177 """
169 from IPython.core.application import BaseIPythonApplication as IPApp
178 from IPython.core.application import BaseIPythonApplication as IPApp
170 try:
179 try:
171 # quick check for absolute path, before going through logic
180 # quick check for absolute path, before going through logic
172 return filefind(filename)
181 return filefind(filename)
173 except IOError:
182 except IOError:
174 pass
183 pass
175
184
176 if profile is None:
185 if profile is None:
177 # profile unspecified, check if running from an IPython app
186 # profile unspecified, check if running from an IPython app
178 if IPApp.initialized():
187 if IPApp.initialized():
179 app = IPApp.instance()
188 app = IPApp.instance()
180 profile_dir = app.profile_dir
189 profile_dir = app.profile_dir
181 else:
190 else:
182 # not running in IPython, use default profile
191 # not running in IPython, use default profile
183 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
192 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
184 else:
193 else:
185 # find profiledir by profile name:
194 # find profiledir by profile name:
186 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
195 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
187 security_dir = profile_dir.security_dir
196 security_dir = profile_dir.security_dir
188
197
189 try:
198 try:
190 # first, try explicit name
199 # first, try explicit name
191 return filefind(filename, ['.', security_dir])
200 return filefind(filename, ['.', security_dir])
192 except IOError:
201 except IOError:
193 pass
202 pass
194
203
195 # not found by full name
204 # not found by full name
196
205
197 if '*' in filename:
206 if '*' in filename:
198 # given as a glob already
207 # given as a glob already
199 pat = filename
208 pat = filename
200 else:
209 else:
201 # accept any substring match
210 # accept any substring match
202 pat = '*%s*' % filename
211 pat = '*%s*' % filename
203 matches = glob.glob( os.path.join(security_dir, pat) )
212 matches = glob.glob( os.path.join(security_dir, pat) )
204 if not matches:
213 if not matches:
205 raise IOError("Could not find %r in %r" % (filename, security_dir))
214 raise IOError("Could not find %r in %r" % (filename, security_dir))
206 elif len(matches) == 1:
215 elif len(matches) == 1:
207 return matches[0]
216 return matches[0]
208 else:
217 else:
209 # get most recent match, by access time:
218 # get most recent match, by access time:
210 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
219 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
211
220
212
221
213 def get_connection_info(connection_file=None, unpack=False, profile=None):
222 def get_connection_info(connection_file=None, unpack=False, profile=None):
214 """Return the connection information for the current Kernel.
223 """Return the connection information for the current Kernel.
215
224
216 Parameters
225 Parameters
217 ----------
226 ----------
218 connection_file : str [optional]
227 connection_file : str [optional]
219 The connection file to be used. Can be given by absolute path, or
228 The connection file to be used. Can be given by absolute path, or
220 IPython will search in the security directory of a given profile.
229 IPython will search in the security directory of a given profile.
221 If run from IPython,
230 If run from IPython,
222
231
223 If unspecified, the connection file for the currently running
232 If unspecified, the connection file for the currently running
224 IPython Kernel will be used, which is only allowed from inside a kernel.
233 IPython Kernel will be used, which is only allowed from inside a kernel.
225 unpack : bool [default: False]
234 unpack : bool [default: False]
226 if True, return the unpacked dict, otherwise just the string contents
235 if True, return the unpacked dict, otherwise just the string contents
227 of the file.
236 of the file.
228 profile : str [optional]
237 profile : str [optional]
229 The name of the profile to use when searching for the connection file,
238 The name of the profile to use when searching for the connection file,
230 if different from the current IPython session or 'default'.
239 if different from the current IPython session or 'default'.
231
240
232
241
233 Returns
242 Returns
234 -------
243 -------
235 The connection dictionary of the current kernel, as string or dict,
244 The connection dictionary of the current kernel, as string or dict,
236 depending on `unpack`.
245 depending on `unpack`.
237 """
246 """
238 if connection_file is None:
247 if connection_file is None:
239 # get connection file from current kernel
248 # get connection file from current kernel
240 cf = get_connection_file()
249 cf = get_connection_file()
241 else:
250 else:
242 # connection file specified, allow shortnames:
251 # connection file specified, allow shortnames:
243 cf = find_connection_file(connection_file, profile=profile)
252 cf = find_connection_file(connection_file, profile=profile)
244
253
245 with open(cf) as f:
254 with open(cf) as f:
246 info = f.read()
255 info = f.read()
247
256
248 if unpack:
257 if unpack:
249 info = json.loads(info)
258 info = json.loads(info)
250 # ensure key is bytes:
259 # ensure key is bytes:
251 info['key'] = str_to_bytes(info.get('key', ''))
260 info['key'] = str_to_bytes(info.get('key', ''))
252 return info
261 return info
253
262
254
263
255 def connect_qtconsole(connection_file=None, argv=None, profile=None):
264 def connect_qtconsole(connection_file=None, argv=None, profile=None):
256 """Connect a qtconsole to the current kernel.
265 """Connect a qtconsole to the current kernel.
257
266
258 This is useful for connecting a second qtconsole to a kernel, or to a
267 This is useful for connecting a second qtconsole to a kernel, or to a
259 local notebook.
268 local notebook.
260
269
261 Parameters
270 Parameters
262 ----------
271 ----------
263 connection_file : str [optional]
272 connection_file : str [optional]
264 The connection file to be used. Can be given by absolute path, or
273 The connection file to be used. Can be given by absolute path, or
265 IPython will search in the security directory of a given profile.
274 IPython will search in the security directory of a given profile.
266 If run from IPython,
275 If run from IPython,
267
276
268 If unspecified, the connection file for the currently running
277 If unspecified, the connection file for the currently running
269 IPython Kernel will be used, which is only allowed from inside a kernel.
278 IPython Kernel will be used, which is only allowed from inside a kernel.
270 argv : list [optional]
279 argv : list [optional]
271 Any extra args to be passed to the console.
280 Any extra args to be passed to the console.
272 profile : str [optional]
281 profile : str [optional]
273 The name of the profile to use when searching for the connection file,
282 The name of the profile to use when searching for the connection file,
274 if different from the current IPython session or 'default'.
283 if different from the current IPython session or 'default'.
275
284
276
285
277 Returns
286 Returns
278 -------
287 -------
279 subprocess.Popen instance running the qtconsole frontend
288 subprocess.Popen instance running the qtconsole frontend
280 """
289 """
281 argv = [] if argv is None else argv
290 argv = [] if argv is None else argv
282
291
283 if connection_file is None:
292 if connection_file is None:
284 # get connection file from current kernel
293 # get connection file from current kernel
285 cf = get_connection_file()
294 cf = get_connection_file()
286 else:
295 else:
287 cf = find_connection_file(connection_file, profile=profile)
296 cf = find_connection_file(connection_file, profile=profile)
288
297
289 cmd = ';'.join([
298 cmd = ';'.join([
290 "from IPython.frontend.qt.console import qtconsoleapp",
299 "from IPython.frontend.qt.console import qtconsoleapp",
291 "qtconsoleapp.main()"
300 "qtconsoleapp.main()"
292 ])
301 ])
293
302
294 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
303 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
295
304
296
305
297 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
306 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
298 """tunnel connections to a kernel via ssh
307 """tunnel connections to a kernel via ssh
299
308
300 This will open four SSH tunnels from localhost on this machine to the
309 This will open four SSH tunnels from localhost on this machine to the
301 ports associated with the kernel. They can be either direct
310 ports associated with the kernel. They can be either direct
302 localhost-localhost tunnels, or if an intermediate server is necessary,
311 localhost-localhost tunnels, or if an intermediate server is necessary,
303 the kernel must be listening on a public IP.
312 the kernel must be listening on a public IP.
304
313
305 Parameters
314 Parameters
306 ----------
315 ----------
307 connection_info : dict or str (path)
316 connection_info : dict or str (path)
308 Either a connection dict, or the path to a JSON connection file
317 Either a connection dict, or the path to a JSON connection file
309 sshserver : str
318 sshserver : str
310 The ssh sever to use to tunnel to the kernel. Can be a full
319 The ssh sever to use to tunnel to the kernel. Can be a full
311 `user@server:port` string. ssh config aliases are respected.
320 `user@server:port` string. ssh config aliases are respected.
312 sshkey : str [optional]
321 sshkey : str [optional]
313 Path to file containing ssh key to use for authentication.
322 Path to file containing ssh key to use for authentication.
314 Only necessary if your ssh config does not already associate
323 Only necessary if your ssh config does not already associate
315 a keyfile with the host.
324 a keyfile with the host.
316
325
317 Returns
326 Returns
318 -------
327 -------
319
328
320 (shell, iopub, stdin, hb) : ints
329 (shell, iopub, stdin, hb) : ints
321 The four ports on localhost that have been forwarded to the kernel.
330 The four ports on localhost that have been forwarded to the kernel.
322 """
331 """
323 if isinstance(connection_info, basestring):
332 if isinstance(connection_info, basestring):
324 # it's a path, unpack it
333 # it's a path, unpack it
325 with open(connection_info) as f:
334 with open(connection_info) as f:
326 connection_info = json.loads(f.read())
335 connection_info = json.loads(f.read())
327
336
328 cf = connection_info
337 cf = connection_info
329
338
330 lports = tunnel.select_random_ports(4)
339 lports = tunnel.select_random_ports(4)
331 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
340 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
332
341
333 remote_ip = cf['ip']
342 remote_ip = cf['ip']
334
343
335 if tunnel.try_passwordless_ssh(sshserver, sshkey):
344 if tunnel.try_passwordless_ssh(sshserver, sshkey):
336 password=False
345 password=False
337 else:
346 else:
338 password = getpass("SSH Password for %s: "%sshserver)
347 password = getpass("SSH Password for %s: "%sshserver)
339
348
340 for lp,rp in zip(lports, rports):
349 for lp,rp in zip(lports, rports):
341 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
350 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
342
351
343 return tuple(lports)
352 return tuple(lports)
344
353
345
354
346 #-----------------------------------------------------------------------------
355 #-----------------------------------------------------------------------------
347 # Mixin for classes that workw ith connection files
356 # Mixin for classes that workw ith connection files
348 #-----------------------------------------------------------------------------
357 #-----------------------------------------------------------------------------
358 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
349
359
350 class ConnectionFileMixin(HasTraits):
360 class ConnectionFileMixin(HasTraits):
351 """Mixin for configurable classes that work with connection files"""
361 """Mixin for configurable classes that work with connection files"""
352
362
353 # The addresses for the communication channels
363 # The addresses for the communication channels
354 connection_file = Unicode('')
364 connection_file = Unicode('')
355 _connection_file_written = Bool(False)
365 _connection_file_written = Bool(False)
356
366
357 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
367 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
358
368
359 ip = Unicode(LOCALHOST, config=True,
369 ip = Unicode(LOCALHOST, config=True,
360 help="""Set the kernel\'s IP address [default localhost].
370 help="""Set the kernel\'s IP address [default localhost].
361 If the IP address is something other than localhost, then
371 If the IP address is something other than localhost, then
362 Consoles on other machines will be able to connect
372 Consoles on other machines will be able to connect
363 to the Kernel, so be careful!"""
373 to the Kernel, so be careful!"""
364 )
374 )
365
375
366 def _ip_default(self):
376 def _ip_default(self):
367 if self.transport == 'ipc':
377 if self.transport == 'ipc':
368 if self.connection_file:
378 if self.connection_file:
369 return os.path.splitext(self.connection_file)[0] + '-ipc'
379 return os.path.splitext(self.connection_file)[0] + '-ipc'
370 else:
380 else:
371 return 'kernel-ipc'
381 return 'kernel-ipc'
372 else:
382 else:
373 return LOCALHOST
383 return LOCALHOST
374
384
375 def _ip_changed(self, name, old, new):
385 def _ip_changed(self, name, old, new):
376 if new == '*':
386 if new == '*':
377 self.ip = '0.0.0.0'
387 self.ip = '0.0.0.0'
378
388
379 # protected traits
389 # protected traits
380
390
381 shell_port = Integer(0)
391 shell_port = Integer(0)
382 iopub_port = Integer(0)
392 iopub_port = Integer(0)
383 stdin_port = Integer(0)
393 stdin_port = Integer(0)
394 control_port = Integer(0)
384 hb_port = Integer(0)
395 hb_port = Integer(0)
385
396
397 @property
398 def ports(self):
399 return [ getattr(self, name) for name in port_names ]
400
386 #--------------------------------------------------------------------------
401 #--------------------------------------------------------------------------
387 # Connection and ipc file management
402 # Connection and ipc file management
388 #--------------------------------------------------------------------------
403 #--------------------------------------------------------------------------
389
404
405 def get_connection_info(self):
406 """return the connection info as a dict"""
407 return dict(
408 transport=self.transport,
409 ip=self.ip,
410 shell_port=self.shell_port,
411 iopub_port=self.iopub_port,
412 stdin_port=self.stdin_port,
413 hb_port=self.hb_port,
414 control_port=self.control_port,
415 )
416
390 def cleanup_connection_file(self):
417 def cleanup_connection_file(self):
391 """Cleanup connection file *if we wrote it*
418 """Cleanup connection file *if we wrote it*
392
419
393 Will not raise if the connection file was already removed somehow.
420 Will not raise if the connection file was already removed somehow.
394 """
421 """
395 if self._connection_file_written:
422 if self._connection_file_written:
396 # cleanup connection files on full shutdown of kernel we started
423 # cleanup connection files on full shutdown of kernel we started
397 self._connection_file_written = False
424 self._connection_file_written = False
398 try:
425 try:
399 os.remove(self.connection_file)
426 os.remove(self.connection_file)
400 except (IOError, OSError, AttributeError):
427 except (IOError, OSError, AttributeError):
401 pass
428 pass
402
429
403 def cleanup_ipc_files(self):
430 def cleanup_ipc_files(self):
404 """Cleanup ipc files if we wrote them."""
431 """Cleanup ipc files if we wrote them."""
405 if self.transport != 'ipc':
432 if self.transport != 'ipc':
406 return
433 return
407 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
434 for port in self.ports:
408 ipcfile = "%s-%i" % (self.ip, port)
435 ipcfile = "%s-%i" % (self.ip, port)
409 try:
436 try:
410 os.remove(ipcfile)
437 os.remove(ipcfile)
411 except (IOError, OSError):
438 except (IOError, OSError):
412 pass
439 pass
413
440
414 def write_connection_file(self):
441 def write_connection_file(self):
415 """Write connection info to JSON dict in self.connection_file."""
442 """Write connection info to JSON dict in self.connection_file."""
416 if self._connection_file_written:
443 if self._connection_file_written:
417 return
444 return
418 self.connection_file,cfg = write_connection_file(self.connection_file,
445
446 self.connection_file, cfg = write_connection_file(self.connection_file,
419 transport=self.transport, ip=self.ip, key=self.session.key,
447 transport=self.transport, ip=self.ip, key=self.session.key,
420 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
448 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
421 shell_port=self.shell_port, hb_port=self.hb_port)
449 shell_port=self.shell_port, hb_port=self.hb_port,
450 control_port=self.control_port,
451 )
422 # write_connection_file also sets default ports:
452 # write_connection_file also sets default ports:
423 self.shell_port = cfg['shell_port']
453 for name in port_names:
424 self.stdin_port = cfg['stdin_port']
454 setattr(self, name, cfg[name])
425 self.iopub_port = cfg['iopub_port']
426 self.hb_port = cfg['hb_port']
427
455
428 self._connection_file_written = True
456 self._connection_file_written = True
429
457
430 def load_connection_file(self):
458 def load_connection_file(self):
431 """Load connection info from JSON dict in self.connection_file."""
459 """Load connection info from JSON dict in self.connection_file."""
432 with open(self.connection_file) as f:
460 with open(self.connection_file) as f:
433 cfg = json.loads(f.read())
461 cfg = json.loads(f.read())
434
462
435 self.transport = cfg.get('transport', 'tcp')
463 self.transport = cfg.get('transport', 'tcp')
436 self.ip = cfg['ip']
464 self.ip = cfg['ip']
437 self.shell_port = cfg['shell_port']
465 for name in port_names:
438 self.stdin_port = cfg['stdin_port']
466 setattr(self, name, cfg[name])
439 self.iopub_port = cfg['iopub_port']
440 self.hb_port = cfg['hb_port']
441 self.session.key = str_to_bytes(cfg['key'])
467 self.session.key = str_to_bytes(cfg['key'])
442
468
443
469
444
470
445 __all__ = [
471 __all__ = [
446 'write_connection_file',
472 'write_connection_file',
447 'get_connection_file',
473 'get_connection_file',
448 'find_connection_file',
474 'find_connection_file',
449 'get_connection_info',
475 'get_connection_info',
450 'connect_qtconsole',
476 'connect_qtconsole',
451 'tunnel_to_kernel',
477 'tunnel_to_kernel',
452 ]
478 ]
@@ -1,385 +1,390 b''
1 """Base class to manage a running kernel
1 """Base class to manage a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 # Standard library imports
17 # Standard library imports
18 import signal
18 import signal
19 import sys
19 import sys
20 import time
20 import time
21
21
22 import zmq
22 import zmq
23
23
24 # Local imports
24 # Local imports
25 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.localinterfaces import LOCAL_IPS
26 from IPython.utils.localinterfaces import LOCAL_IPS
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Any, Instance, Unicode, List, Bool,
28 Any, Instance, Unicode, List, Bool,
29 )
29 )
30 from IPython.kernel import (
30 from IPython.kernel import (
31 make_ipkernel_cmd,
31 make_ipkernel_cmd,
32 launch_kernel,
32 launch_kernel,
33 )
33 )
34 from .connect import ConnectionFileMixin
34 from .connect import ConnectionFileMixin
35 from .zmq.session import Session
35 from .zmq.session import Session
36 from .managerabc import (
36 from .managerabc import (
37 KernelManagerABC
37 KernelManagerABC
38 )
38 )
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Main kernel manager class
41 # Main kernel manager class
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 _socket_types = {
44 _socket_types = {
45 'hb' : zmq.REQ,
45 'hb' : zmq.REQ,
46 'shell' : zmq.DEALER,
46 'shell' : zmq.DEALER,
47 'iopub' : zmq.SUB,
47 'iopub' : zmq.SUB,
48 'stdin' : zmq.DEALER,
48 'stdin' : zmq.DEALER,
49 'control': zmq.DEALER,
49 }
50 }
50
51
51 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
52 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
52 """Manages a single kernel in a subprocess on this host.
53 """Manages a single kernel in a subprocess on this host.
53
54
54 This version starts kernels with Popen.
55 This version starts kernels with Popen.
55 """
56 """
56
57
57 # The PyZMQ Context to use for communication with the kernel.
58 # The PyZMQ Context to use for communication with the kernel.
58 context = Instance(zmq.Context)
59 context = Instance(zmq.Context)
59 def _context_default(self):
60 def _context_default(self):
60 return zmq.Context.instance()
61 return zmq.Context.instance()
61
62
62 # The Session to use for communication with the kernel.
63 # The Session to use for communication with the kernel.
63 session = Instance(Session)
64 session = Instance(Session)
64 def _session_default(self):
65 def _session_default(self):
65 return Session(config=self.config)
66 return Session(config=self.config)
66
67
67 # The kernel process with which the KernelManager is communicating.
68 # The kernel process with which the KernelManager is communicating.
68 # generally a Popen instance
69 # generally a Popen instance
69 kernel = Any()
70 kernel = Any()
70
71
71 kernel_cmd = List(Unicode, config=True,
72 kernel_cmd = List(Unicode, config=True,
72 help="""The Popen Command to launch the kernel.
73 help="""The Popen Command to launch the kernel.
73 Override this if you have a custom
74 Override this if you have a custom
74 """
75 """
75 )
76 )
76
77
77 def _kernel_cmd_changed(self, name, old, new):
78 def _kernel_cmd_changed(self, name, old, new):
78 self.ipython_kernel = False
79 self.ipython_kernel = False
79
80
80 ipython_kernel = Bool(True)
81 ipython_kernel = Bool(True)
81
82
82 # Protected traits
83 # Protected traits
83 _launch_args = Any
84 _launch_args = Any()
85 _control_socket = Any()
84
86
85 autorestart = Bool(False, config=True,
87 autorestart = Bool(False, config=True,
86 help="""Should we autorestart the kernel if it dies."""
88 help="""Should we autorestart the kernel if it dies."""
87 )
89 )
88
90
89 def __del__(self):
91 def __del__(self):
92 self._close_control_socket()
90 self.cleanup_connection_file()
93 self.cleanup_connection_file()
91
94
92 #--------------------------------------------------------------------------
95 #--------------------------------------------------------------------------
93 # Kernel restarter
96 # Kernel restarter
94 #--------------------------------------------------------------------------
97 #--------------------------------------------------------------------------
95
98
96 def start_restarter(self):
99 def start_restarter(self):
97 pass
100 pass
98
101
99 def stop_restarter(self):
102 def stop_restarter(self):
100 pass
103 pass
101
104
102 #--------------------------------------------------------------------------
105 #--------------------------------------------------------------------------
103 # Connection info
106 # Connection info
104 #--------------------------------------------------------------------------
107 #--------------------------------------------------------------------------
105
108
106 def get_connection_info(self):
107 """return the connection info as a dict"""
108 return dict(
109 transport=self.transport,
110 ip=self.ip,
111 shell_port=self.shell_port,
112 iopub_port=self.iopub_port,
113 stdin_port=self.stdin_port,
114 hb_port=self.hb_port,
115 )
116
117 def _make_url(self, channel):
109 def _make_url(self, channel):
118 """Make a ZeroMQ URL for a given channel."""
110 """Make a ZeroMQ URL for a given channel."""
119 transport = self.transport
111 transport = self.transport
120 ip = self.ip
112 ip = self.ip
121 port = getattr(self, '%s_port' % channel)
113 port = getattr(self, '%s_port' % channel)
122
114
123 if transport == 'tcp':
115 if transport == 'tcp':
124 return "tcp://%s:%i" % (ip, port)
116 return "tcp://%s:%i" % (ip, port)
125 else:
117 else:
126 return "%s://%s-%s" % (transport, ip, port)
118 return "%s://%s-%s" % (transport, ip, port)
127
119
128 def _create_connected_socket(self, channel):
120 def _create_connected_socket(self, channel):
129 """Create a zmq Socket and connect it to the kernel."""
121 """Create a zmq Socket and connect it to the kernel."""
130 url = self._make_url(channel)
122 url = self._make_url(channel)
131 socket_type = _socket_types[channel]
123 socket_type = _socket_types[channel]
132 sock = self.context.socket(socket_type)
124 sock = self.context.socket(socket_type)
133 self.log.info("Connecting to: %s" % url)
125 self.log.info("Connecting to: %s" % url)
134 sock.connect(url)
126 sock.connect(url)
135 return sock
127 return sock
136
128
137 def connect_iopub(self):
129 def connect_iopub(self):
138 """return zmq Socket connected to the IOPub channel"""
130 """return zmq Socket connected to the IOPub channel"""
139 sock = self._create_connected_socket('iopub')
131 sock = self._create_connected_socket('iopub')
140 sock.setsockopt(zmq.SUBSCRIBE, b'')
132 sock.setsockopt(zmq.SUBSCRIBE, b'')
141 return sock
133 return sock
142
134
143 def connect_shell(self):
135 def connect_shell(self):
144 """return zmq Socket connected to the Shell channel"""
136 """return zmq Socket connected to the Shell channel"""
145 return self._create_connected_socket('shell')
137 return self._create_connected_socket('shell')
146
138
147 def connect_stdin(self):
139 def connect_stdin(self):
148 """return zmq Socket connected to the StdIn channel"""
140 """return zmq Socket connected to the StdIn channel"""
149 return self._create_connected_socket('stdin')
141 return self._create_connected_socket('stdin')
150
142
151 def connect_hb(self):
143 def connect_hb(self):
152 """return zmq Socket connected to the Heartbeat channel"""
144 """return zmq Socket connected to the Heartbeat channel"""
153 return self._create_connected_socket('hb')
145 return self._create_connected_socket('hb')
154
146
147 def connect_control(self):
148 """return zmq Socket connected to the Heartbeat channel"""
149 return self._create_connected_socket('control')
150
155 #--------------------------------------------------------------------------
151 #--------------------------------------------------------------------------
156 # Kernel management
152 # Kernel management
157 #--------------------------------------------------------------------------
153 #--------------------------------------------------------------------------
158
154
159 def format_kernel_cmd(self, **kw):
155 def format_kernel_cmd(self, **kw):
160 """format templated args (e.g. {connection_file})"""
156 """format templated args (e.g. {connection_file})"""
161 if self.kernel_cmd:
157 if self.kernel_cmd:
162 cmd = self.kernel_cmd
158 cmd = self.kernel_cmd
163 else:
159 else:
164 cmd = make_ipkernel_cmd(
160 cmd = make_ipkernel_cmd(
165 'from IPython.kernel.zmq.kernelapp import main; main()',
161 'from IPython.kernel.zmq.kernelapp import main; main()',
166 **kw
162 **kw
167 )
163 )
168 ns = dict(connection_file=self.connection_file)
164 ns = dict(connection_file=self.connection_file)
169 ns.update(self._launch_args)
165 ns.update(self._launch_args)
170 return [ c.format(**ns) for c in cmd ]
166 return [ c.format(**ns) for c in cmd ]
171
167
172 def _launch_kernel(self, kernel_cmd, **kw):
168 def _launch_kernel(self, kernel_cmd, **kw):
173 """actually launch the kernel
169 """actually launch the kernel
174
170
175 override in a subclass to launch kernel subprocesses differently
171 override in a subclass to launch kernel subprocesses differently
176 """
172 """
177 return launch_kernel(kernel_cmd, **kw)
173 return launch_kernel(kernel_cmd, **kw)
178
174
175 def _connect_control_socket(self):
176 if self._control_socket is None:
177 self._control_socket = self.connect_control()
178
179 def _close_control_socket(self):
180 if self._control_socket is None:
181 return
182 self._control_socket.linger = 100
183 self._control_socket.close()
184 self._control_socket = None
185
186
179 def start_kernel(self, **kw):
187 def start_kernel(self, **kw):
180 """Starts a kernel on this host in a separate process.
188 """Starts a kernel on this host in a separate process.
181
189
182 If random ports (port=0) are being used, this method must be called
190 If random ports (port=0) are being used, this method must be called
183 before the channels are created.
191 before the channels are created.
184
192
185 Parameters:
193 Parameters:
186 -----------
194 -----------
187 **kw : optional
195 **kw : optional
188 keyword arguments that are passed down to build the kernel_cmd
196 keyword arguments that are passed down to build the kernel_cmd
189 and launching the kernel (e.g. Popen kwargs).
197 and launching the kernel (e.g. Popen kwargs).
190 """
198 """
191 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
199 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
192 raise RuntimeError("Can only launch a kernel on a local interface. "
200 raise RuntimeError("Can only launch a kernel on a local interface. "
193 "Make sure that the '*_address' attributes are "
201 "Make sure that the '*_address' attributes are "
194 "configured properly. "
202 "configured properly. "
195 "Currently valid addresses are: %s"%LOCAL_IPS
203 "Currently valid addresses are: %s"%LOCAL_IPS
196 )
204 )
197
205
198 # write connection file / get default ports
206 # write connection file / get default ports
199 self.write_connection_file()
207 self.write_connection_file()
200
208
201 # save kwargs for use in restart
209 # save kwargs for use in restart
202 self._launch_args = kw.copy()
210 self._launch_args = kw.copy()
203 # build the Popen cmd
211 # build the Popen cmd
204 kernel_cmd = self.format_kernel_cmd(**kw)
212 kernel_cmd = self.format_kernel_cmd(**kw)
205 # launch the kernel subprocess
213 # launch the kernel subprocess
206 self.kernel = self._launch_kernel(kernel_cmd,
214 self.kernel = self._launch_kernel(kernel_cmd,
207 ipython_kernel=self.ipython_kernel,
215 ipython_kernel=self.ipython_kernel,
208 **kw)
216 **kw)
209 self.start_restarter()
217 self.start_restarter()
218 self._connect_control_socket()
210
219
211 def _send_shutdown_request(self, restart=False):
220 def _send_shutdown_request(self, restart=False):
212 """TODO: send a shutdown request via control channel"""
221 """TODO: send a shutdown request via control channel"""
213 raise NotImplementedError("Soft shutdown needs control channel")
222 content = dict(restart=restart)
223 msg = self.session.msg("shutdown_request", content=content)
224 self.session.send(self._control_socket, msg)
214
225
215 def shutdown_kernel(self, now=False, restart=False):
226 def shutdown_kernel(self, now=False, restart=False):
216 """Attempts to the stop the kernel process cleanly.
227 """Attempts to the stop the kernel process cleanly.
217
228
218 This attempts to shutdown the kernels cleanly by:
229 This attempts to shutdown the kernels cleanly by:
219
230
220 1. Sending it a shutdown message over the shell channel.
231 1. Sending it a shutdown message over the shell channel.
221 2. If that fails, the kernel is shutdown forcibly by sending it
232 2. If that fails, the kernel is shutdown forcibly by sending it
222 a signal.
233 a signal.
223
234
224 Parameters:
235 Parameters:
225 -----------
236 -----------
226 now : bool
237 now : bool
227 Should the kernel be forcible killed *now*. This skips the
238 Should the kernel be forcible killed *now*. This skips the
228 first, nice shutdown attempt.
239 first, nice shutdown attempt.
229 restart: bool
240 restart: bool
230 Will this kernel be restarted after it is shutdown. When this
241 Will this kernel be restarted after it is shutdown. When this
231 is True, connection files will not be cleaned up.
242 is True, connection files will not be cleaned up.
232 """
243 """
233
234 # Stop monitoring for restarting while we shutdown.
244 # Stop monitoring for restarting while we shutdown.
235 self.stop_restarter()
245 self.stop_restarter()
236
246
237 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
247 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
238 if sys.platform == 'win32':
248 if sys.platform == 'win32':
239 self._kill_kernel()
249 self._kill_kernel()
240 return
250 return
241
251
242 # bypass clean shutdown while
243 # FIXME: add control channel for clean shutdown
244 now = True
245
246 if now:
252 if now:
247 if self.has_kernel:
253 if self.has_kernel:
248 self._kill_kernel()
254 self._kill_kernel()
249 else:
255 else:
250 # Don't send any additional kernel kill messages immediately, to give
256 # Don't send any additional kernel kill messages immediately, to give
251 # the kernel a chance to properly execute shutdown actions. Wait for at
257 # the kernel a chance to properly execute shutdown actions. Wait for at
252 # most 1s, checking every 0.1s.
258 # most 1s, checking every 0.1s.
253 # FIXME: this method is not yet implemented (need Control channel)
254 self._send_shutdown_request(restart=restart)
259 self._send_shutdown_request(restart=restart)
255 for i in range(10):
260 for i in range(10):
256 if self.is_alive():
261 if self.is_alive():
257 time.sleep(0.1)
262 time.sleep(0.1)
258 else:
263 else:
259 break
264 break
260 else:
265 else:
261 # OK, we've waited long enough.
266 # OK, we've waited long enough.
262 if self.has_kernel:
267 if self.has_kernel:
263 self._kill_kernel()
268 self._kill_kernel()
264
269
265 if not restart:
270 if not restart:
266 self.cleanup_connection_file()
271 self.cleanup_connection_file()
267 self.cleanup_ipc_files()
272 self.cleanup_ipc_files()
268 else:
273 else:
269 self.cleanup_ipc_files()
274 self.cleanup_ipc_files()
270
275
271 def restart_kernel(self, now=False, **kw):
276 def restart_kernel(self, now=False, **kw):
272 """Restarts a kernel with the arguments that were used to launch it.
277 """Restarts a kernel with the arguments that were used to launch it.
273
278
274 If the old kernel was launched with random ports, the same ports will be
279 If the old kernel was launched with random ports, the same ports will be
275 used for the new kernel. The same connection file is used again.
280 used for the new kernel. The same connection file is used again.
276
281
277 Parameters
282 Parameters
278 ----------
283 ----------
279 now : bool, optional
284 now : bool, optional
280 If True, the kernel is forcefully restarted *immediately*, without
285 If True, the kernel is forcefully restarted *immediately*, without
281 having a chance to do any cleanup action. Otherwise the kernel is
286 having a chance to do any cleanup action. Otherwise the kernel is
282 given 1s to clean up before a forceful restart is issued.
287 given 1s to clean up before a forceful restart is issued.
283
288
284 In all cases the kernel is restarted, the only difference is whether
289 In all cases the kernel is restarted, the only difference is whether
285 it is given a chance to perform a clean shutdown or not.
290 it is given a chance to perform a clean shutdown or not.
286
291
287 **kw : optional
292 **kw : optional
288 Any options specified here will overwrite those used to launch the
293 Any options specified here will overwrite those used to launch the
289 kernel.
294 kernel.
290 """
295 """
291 if self._launch_args is None:
296 if self._launch_args is None:
292 raise RuntimeError("Cannot restart the kernel. "
297 raise RuntimeError("Cannot restart the kernel. "
293 "No previous call to 'start_kernel'.")
298 "No previous call to 'start_kernel'.")
294 else:
299 else:
295 # Stop currently running kernel.
300 # Stop currently running kernel.
296 self.shutdown_kernel(now=now, restart=True)
301 self.shutdown_kernel(now=now, restart=True)
297
302
298 # Start new kernel.
303 # Start new kernel.
299 self._launch_args.update(kw)
304 self._launch_args.update(kw)
300 self.start_kernel(**self._launch_args)
305 self.start_kernel(**self._launch_args)
301
306
302 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
307 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
303 # unless there is some delay here.
308 # unless there is some delay here.
304 if sys.platform == 'win32':
309 if sys.platform == 'win32':
305 time.sleep(0.2)
310 time.sleep(0.2)
306
311
307 @property
312 @property
308 def has_kernel(self):
313 def has_kernel(self):
309 """Has a kernel been started that we are managing."""
314 """Has a kernel been started that we are managing."""
310 return self.kernel is not None
315 return self.kernel is not None
311
316
312 def _kill_kernel(self):
317 def _kill_kernel(self):
313 """Kill the running kernel.
318 """Kill the running kernel.
314
319
315 This is a private method, callers should use shutdown_kernel(now=True).
320 This is a private method, callers should use shutdown_kernel(now=True).
316 """
321 """
317 if self.has_kernel:
322 if self.has_kernel:
318
323
319 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
324 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
320 # TerminateProcess() on Win32).
325 # TerminateProcess() on Win32).
321 try:
326 try:
322 self.kernel.kill()
327 self.kernel.kill()
323 except OSError as e:
328 except OSError as e:
324 # In Windows, we will get an Access Denied error if the process
329 # In Windows, we will get an Access Denied error if the process
325 # has already terminated. Ignore it.
330 # has already terminated. Ignore it.
326 if sys.platform == 'win32':
331 if sys.platform == 'win32':
327 if e.winerror != 5:
332 if e.winerror != 5:
328 raise
333 raise
329 # On Unix, we may get an ESRCH error if the process has already
334 # On Unix, we may get an ESRCH error if the process has already
330 # terminated. Ignore it.
335 # terminated. Ignore it.
331 else:
336 else:
332 from errno import ESRCH
337 from errno import ESRCH
333 if e.errno != ESRCH:
338 if e.errno != ESRCH:
334 raise
339 raise
335
340
336 # Block until the kernel terminates.
341 # Block until the kernel terminates.
337 self.kernel.wait()
342 self.kernel.wait()
338 self.kernel = None
343 self.kernel = None
339 else:
344 else:
340 raise RuntimeError("Cannot kill kernel. No kernel is running!")
345 raise RuntimeError("Cannot kill kernel. No kernel is running!")
341
346
342 def interrupt_kernel(self):
347 def interrupt_kernel(self):
343 """Interrupts the kernel by sending it a signal.
348 """Interrupts the kernel by sending it a signal.
344
349
345 Unlike ``signal_kernel``, this operation is well supported on all
350 Unlike ``signal_kernel``, this operation is well supported on all
346 platforms.
351 platforms.
347 """
352 """
348 if self.has_kernel:
353 if self.has_kernel:
349 if sys.platform == 'win32':
354 if sys.platform == 'win32':
350 from .zmq.parentpoller import ParentPollerWindows as Poller
355 from .zmq.parentpoller import ParentPollerWindows as Poller
351 Poller.send_interrupt(self.kernel.win32_interrupt_event)
356 Poller.send_interrupt(self.kernel.win32_interrupt_event)
352 else:
357 else:
353 self.kernel.send_signal(signal.SIGINT)
358 self.kernel.send_signal(signal.SIGINT)
354 else:
359 else:
355 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
360 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
356
361
357 def signal_kernel(self, signum):
362 def signal_kernel(self, signum):
358 """Sends a signal to the kernel.
363 """Sends a signal to the kernel.
359
364
360 Note that since only SIGTERM is supported on Windows, this function is
365 Note that since only SIGTERM is supported on Windows, this function is
361 only useful on Unix systems.
366 only useful on Unix systems.
362 """
367 """
363 if self.has_kernel:
368 if self.has_kernel:
364 self.kernel.send_signal(signum)
369 self.kernel.send_signal(signum)
365 else:
370 else:
366 raise RuntimeError("Cannot signal kernel. No kernel is running!")
371 raise RuntimeError("Cannot signal kernel. No kernel is running!")
367
372
368 def is_alive(self):
373 def is_alive(self):
369 """Is the kernel process still running?"""
374 """Is the kernel process still running?"""
370 if self.has_kernel:
375 if self.has_kernel:
371 if self.kernel.poll() is None:
376 if self.kernel.poll() is None:
372 return True
377 return True
373 else:
378 else:
374 return False
379 return False
375 else:
380 else:
376 # we don't have a kernel
381 # we don't have a kernel
377 return False
382 return False
378
383
379
384
380 #-----------------------------------------------------------------------------
385 #-----------------------------------------------------------------------------
381 # ABC Registration
386 # ABC Registration
382 #-----------------------------------------------------------------------------
387 #-----------------------------------------------------------------------------
383
388
384 KernelManagerABC.register(KernelManager)
389 KernelManagerABC.register(KernelManager)
385
390
@@ -1,439 +1,447 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 from __future__ import print_function
18 from __future__ import print_function
19
19
20 # Standard library imports
20 # Standard library imports
21 import atexit
21 import atexit
22 import json
22 import json
23 import os
23 import os
24 import sys
24 import sys
25 import signal
25 import signal
26
26
27 # System library imports
27 # System library imports
28 import zmq
28 import zmq
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30 from zmq.eventloop.zmqstream import ZMQStream
30 from zmq.eventloop.zmqstream import ZMQStream
31
31
32 # IPython imports
32 # IPython imports
33 from IPython.core.ultratb import FormattedTB
33 from IPython.core.ultratb import FormattedTB
34 from IPython.core.application import (
34 from IPython.core.application import (
35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
36 )
36 )
37 from IPython.core.profiledir import ProfileDir
37 from IPython.core.profiledir import ProfileDir
38 from IPython.core.shellapp import (
38 from IPython.core.shellapp import (
39 InteractiveShellApp, shell_flags, shell_aliases
39 InteractiveShellApp, shell_flags, shell_aliases
40 )
40 )
41 from IPython.utils import io
41 from IPython.utils import io
42 from IPython.utils.localinterfaces import LOCALHOST
42 from IPython.utils.localinterfaces import LOCALHOST
43 from IPython.utils.path import filefind
43 from IPython.utils.path import filefind
44 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.utils.traitlets import (
45 from IPython.utils.traitlets import (
46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
47 DottedObjectName,
47 DottedObjectName,
48 )
48 )
49 from IPython.utils.importstring import import_item
49 from IPython.utils.importstring import import_item
50 from IPython.kernel import write_connection_file
50 from IPython.kernel import write_connection_file
51
51
52 # local imports
52 # local imports
53 from heartbeat import Heartbeat
53 from heartbeat import Heartbeat
54 from ipkernel import Kernel
54 from ipkernel import Kernel
55 from parentpoller import ParentPollerUnix, ParentPollerWindows
55 from parentpoller import ParentPollerUnix, ParentPollerWindows
56 from session import (
56 from session import (
57 Session, session_flags, session_aliases, default_secure,
57 Session, session_flags, session_aliases, default_secure,
58 )
58 )
59 from zmqshell import ZMQInteractiveShell
59 from zmqshell import ZMQInteractiveShell
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Flags and Aliases
62 # Flags and Aliases
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 kernel_aliases = dict(base_aliases)
65 kernel_aliases = dict(base_aliases)
66 kernel_aliases.update({
66 kernel_aliases.update({
67 'ip' : 'IPKernelApp.ip',
67 'ip' : 'IPKernelApp.ip',
68 'hb' : 'IPKernelApp.hb_port',
68 'hb' : 'IPKernelApp.hb_port',
69 'shell' : 'IPKernelApp.shell_port',
69 'shell' : 'IPKernelApp.shell_port',
70 'iopub' : 'IPKernelApp.iopub_port',
70 'iopub' : 'IPKernelApp.iopub_port',
71 'stdin' : 'IPKernelApp.stdin_port',
71 'stdin' : 'IPKernelApp.stdin_port',
72 'control' : 'IPKernelApp.control_port',
72 'f' : 'IPKernelApp.connection_file',
73 'f' : 'IPKernelApp.connection_file',
73 'parent': 'IPKernelApp.parent',
74 'parent': 'IPKernelApp.parent',
74 'transport': 'IPKernelApp.transport',
75 'transport': 'IPKernelApp.transport',
75 })
76 })
76 if sys.platform.startswith('win'):
77 if sys.platform.startswith('win'):
77 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
78 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
78
79
79 kernel_flags = dict(base_flags)
80 kernel_flags = dict(base_flags)
80 kernel_flags.update({
81 kernel_flags.update({
81 'no-stdout' : (
82 'no-stdout' : (
82 {'IPKernelApp' : {'no_stdout' : True}},
83 {'IPKernelApp' : {'no_stdout' : True}},
83 "redirect stdout to the null device"),
84 "redirect stdout to the null device"),
84 'no-stderr' : (
85 'no-stderr' : (
85 {'IPKernelApp' : {'no_stderr' : True}},
86 {'IPKernelApp' : {'no_stderr' : True}},
86 "redirect stderr to the null device"),
87 "redirect stderr to the null device"),
87 'pylab' : (
88 'pylab' : (
88 {'IPKernelApp' : {'pylab' : 'auto'}},
89 {'IPKernelApp' : {'pylab' : 'auto'}},
89 """Pre-load matplotlib and numpy for interactive use with
90 """Pre-load matplotlib and numpy for interactive use with
90 the default matplotlib backend."""),
91 the default matplotlib backend."""),
91 })
92 })
92
93
93 # inherit flags&aliases for any IPython shell apps
94 # inherit flags&aliases for any IPython shell apps
94 kernel_aliases.update(shell_aliases)
95 kernel_aliases.update(shell_aliases)
95 kernel_flags.update(shell_flags)
96 kernel_flags.update(shell_flags)
96
97
97 # inherit flags&aliases for Sessions
98 # inherit flags&aliases for Sessions
98 kernel_aliases.update(session_aliases)
99 kernel_aliases.update(session_aliases)
99 kernel_flags.update(session_flags)
100 kernel_flags.update(session_flags)
100
101
101 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
102 # Application class for starting an IPython Kernel
103 # Application class for starting an IPython Kernel
103 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
104
105
105 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
106 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
106 name='ipkernel'
107 name='ipkernel'
107 aliases = Dict(kernel_aliases)
108 aliases = Dict(kernel_aliases)
108 flags = Dict(kernel_flags)
109 flags = Dict(kernel_flags)
109 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
110 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
110 # the kernel class, as an importstring
111 # the kernel class, as an importstring
111 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
112 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
112 help="""The Kernel subclass to be used.
113 help="""The Kernel subclass to be used.
113
114
114 This should allow easy re-use of the IPKernelApp entry point
115 This should allow easy re-use of the IPKernelApp entry point
115 to configure and launch kernels other than IPython's own.
116 to configure and launch kernels other than IPython's own.
116 """)
117 """)
117 kernel = Any()
118 kernel = Any()
118 poller = Any() # don't restrict this even though current pollers are all Threads
119 poller = Any() # don't restrict this even though current pollers are all Threads
119 heartbeat = Instance(Heartbeat)
120 heartbeat = Instance(Heartbeat)
120 session = Instance('IPython.kernel.zmq.session.Session')
121 session = Instance('IPython.kernel.zmq.session.Session')
121 ports = Dict()
122 ports = Dict()
122
123
123 # inherit config file name from parent:
124 # inherit config file name from parent:
124 parent_appname = Unicode(config=True)
125 parent_appname = Unicode(config=True)
125 def _parent_appname_changed(self, name, old, new):
126 def _parent_appname_changed(self, name, old, new):
126 if self.config_file_specified:
127 if self.config_file_specified:
127 # it was manually specified, ignore
128 # it was manually specified, ignore
128 return
129 return
129 self.config_file_name = new.replace('-','_') + u'_config.py'
130 self.config_file_name = new.replace('-','_') + u'_config.py'
130 # don't let this count as specifying the config file
131 # don't let this count as specifying the config file
131 self.config_file_specified = False
132 self.config_file_specified = False
132
133
133 # connection info:
134 # connection info:
134 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
135 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
135 ip = Unicode(config=True,
136 ip = Unicode(config=True,
136 help="Set the IP or interface on which the kernel will listen.")
137 help="Set the IP or interface on which the kernel will listen.")
137 def _ip_default(self):
138 def _ip_default(self):
138 if self.transport == 'ipc':
139 if self.transport == 'ipc':
139 if self.connection_file:
140 if self.connection_file:
140 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
141 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
141 else:
142 else:
142 return 'kernel-ipc'
143 return 'kernel-ipc'
143 else:
144 else:
144 return LOCALHOST
145 return LOCALHOST
145 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
146 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
146 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
147 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
147 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
148 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
148 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
149 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
150 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
149 connection_file = Unicode('', config=True,
151 connection_file = Unicode('', config=True,
150 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
152 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
151
153
152 This file will contain the IP, ports, and authentication key needed to connect
154 This file will contain the IP, ports, and authentication key needed to connect
153 clients to this kernel. By default, this file will be created in the security dir
155 clients to this kernel. By default, this file will be created in the security dir
154 of the current profile, but can be specified by absolute path.
156 of the current profile, but can be specified by absolute path.
155 """)
157 """)
156 @property
158 @property
157 def abs_connection_file(self):
159 def abs_connection_file(self):
158 if os.path.basename(self.connection_file) == self.connection_file:
160 if os.path.basename(self.connection_file) == self.connection_file:
159 return os.path.join(self.profile_dir.security_dir, self.connection_file)
161 return os.path.join(self.profile_dir.security_dir, self.connection_file)
160 else:
162 else:
161 return self.connection_file
163 return self.connection_file
162
164
163
165
164 # streams, etc.
166 # streams, etc.
165 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
167 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
166 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
168 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
167 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
169 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
168 config=True, help="The importstring for the OutStream factory")
170 config=True, help="The importstring for the OutStream factory")
169 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
171 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
170 config=True, help="The importstring for the DisplayHook factory")
172 config=True, help="The importstring for the DisplayHook factory")
171
173
172 # polling
174 # polling
173 parent = Integer(0, config=True,
175 parent = Integer(0, config=True,
174 help="""kill this process if its parent dies. On Windows, the argument
176 help="""kill this process if its parent dies. On Windows, the argument
175 specifies the HANDLE of the parent process, otherwise it is simply boolean.
177 specifies the HANDLE of the parent process, otherwise it is simply boolean.
176 """)
178 """)
177 interrupt = Integer(0, config=True,
179 interrupt = Integer(0, config=True,
178 help="""ONLY USED ON WINDOWS
180 help="""ONLY USED ON WINDOWS
179 Interrupt this process when the parent is signaled.
181 Interrupt this process when the parent is signaled.
180 """)
182 """)
181
183
182 def init_crash_handler(self):
184 def init_crash_handler(self):
183 # Install minimal exception handling
185 # Install minimal exception handling
184 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
186 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
185 ostream=sys.__stdout__)
187 ostream=sys.__stdout__)
186
188
187 def init_poller(self):
189 def init_poller(self):
188 if sys.platform == 'win32':
190 if sys.platform == 'win32':
189 if self.interrupt or self.parent:
191 if self.interrupt or self.parent:
190 self.poller = ParentPollerWindows(self.interrupt, self.parent)
192 self.poller = ParentPollerWindows(self.interrupt, self.parent)
191 elif self.parent:
193 elif self.parent:
192 self.poller = ParentPollerUnix()
194 self.poller = ParentPollerUnix()
193
195
194 def _bind_socket(self, s, port):
196 def _bind_socket(self, s, port):
195 iface = '%s://%s' % (self.transport, self.ip)
197 iface = '%s://%s' % (self.transport, self.ip)
196 if self.transport == 'tcp':
198 if self.transport == 'tcp':
197 if port <= 0:
199 if port <= 0:
198 port = s.bind_to_random_port(iface)
200 port = s.bind_to_random_port(iface)
199 else:
201 else:
200 s.bind("tcp://%s:%i" % (self.ip, port))
202 s.bind("tcp://%s:%i" % (self.ip, port))
201 elif self.transport == 'ipc':
203 elif self.transport == 'ipc':
202 if port <= 0:
204 if port <= 0:
203 port = 1
205 port = 1
204 path = "%s-%i" % (self.ip, port)
206 path = "%s-%i" % (self.ip, port)
205 while os.path.exists(path):
207 while os.path.exists(path):
206 port = port + 1
208 port = port + 1
207 path = "%s-%i" % (self.ip, port)
209 path = "%s-%i" % (self.ip, port)
208 else:
210 else:
209 path = "%s-%i" % (self.ip, port)
211 path = "%s-%i" % (self.ip, port)
210 s.bind("ipc://%s" % path)
212 s.bind("ipc://%s" % path)
211 return port
213 return port
212
214
213 def load_connection_file(self):
215 def load_connection_file(self):
214 """load ip/port/hmac config from JSON connection file"""
216 """load ip/port/hmac config from JSON connection file"""
215 try:
217 try:
216 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
218 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
217 except IOError:
219 except IOError:
218 self.log.debug("Connection file not found: %s", self.connection_file)
220 self.log.debug("Connection file not found: %s", self.connection_file)
219 # This means I own it, so I will clean it up:
221 # This means I own it, so I will clean it up:
220 atexit.register(self.cleanup_connection_file)
222 atexit.register(self.cleanup_connection_file)
221 return
223 return
222 self.log.debug(u"Loading connection file %s", fname)
224 self.log.debug(u"Loading connection file %s", fname)
223 with open(fname) as f:
225 with open(fname) as f:
224 s = f.read()
226 s = f.read()
225 cfg = json.loads(s)
227 cfg = json.loads(s)
226 self.transport = cfg.get('transport', self.transport)
228 self.transport = cfg.get('transport', self.transport)
227 if self.ip == self._ip_default() and 'ip' in cfg:
229 if self.ip == self._ip_default() and 'ip' in cfg:
228 # not overridden by config or cl_args
230 # not overridden by config or cl_args
229 self.ip = cfg['ip']
231 self.ip = cfg['ip']
230 for channel in ('hb', 'shell', 'iopub', 'stdin'):
232 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
231 name = channel + '_port'
233 name = channel + '_port'
232 if getattr(self, name) == 0 and name in cfg:
234 if getattr(self, name) == 0 and name in cfg:
233 # not overridden by config or cl_args
235 # not overridden by config or cl_args
234 setattr(self, name, cfg[name])
236 setattr(self, name, cfg[name])
235 if 'key' in cfg:
237 if 'key' in cfg:
236 self.config.Session.key = str_to_bytes(cfg['key'])
238 self.config.Session.key = str_to_bytes(cfg['key'])
237
239
238 def write_connection_file(self):
240 def write_connection_file(self):
239 """write connection info to JSON file"""
241 """write connection info to JSON file"""
240 cf = self.abs_connection_file
242 cf = self.abs_connection_file
241 self.log.debug("Writing connection file: %s", cf)
243 self.log.debug("Writing connection file: %s", cf)
242 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
244 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
243 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
245 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
244 iopub_port=self.iopub_port)
246 iopub_port=self.iopub_port, control_port=self.control_port)
245
247
246 def cleanup_connection_file(self):
248 def cleanup_connection_file(self):
247 cf = self.abs_connection_file
249 cf = self.abs_connection_file
248 self.log.debug("Cleaning up connection file: %s", cf)
250 self.log.debug("Cleaning up connection file: %s", cf)
249 try:
251 try:
250 os.remove(cf)
252 os.remove(cf)
251 except (IOError, OSError):
253 except (IOError, OSError):
252 pass
254 pass
253
255
254 self.cleanup_ipc_files()
256 self.cleanup_ipc_files()
255
257
256 def cleanup_ipc_files(self):
258 def cleanup_ipc_files(self):
257 """cleanup ipc files if we wrote them"""
259 """cleanup ipc files if we wrote them"""
258 if self.transport != 'ipc':
260 if self.transport != 'ipc':
259 return
261 return
260 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
262 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
261 ipcfile = "%s-%i" % (self.ip, port)
263 ipcfile = "%s-%i" % (self.ip, port)
262 try:
264 try:
263 os.remove(ipcfile)
265 os.remove(ipcfile)
264 except (IOError, OSError):
266 except (IOError, OSError):
265 pass
267 pass
266
268
267 def init_connection_file(self):
269 def init_connection_file(self):
268 if not self.connection_file:
270 if not self.connection_file:
269 self.connection_file = "kernel-%s.json"%os.getpid()
271 self.connection_file = "kernel-%s.json"%os.getpid()
270 try:
272 try:
271 self.load_connection_file()
273 self.load_connection_file()
272 except Exception:
274 except Exception:
273 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
275 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
274 self.exit(1)
276 self.exit(1)
275
277
276 def init_sockets(self):
278 def init_sockets(self):
277 # Create a context, a session, and the kernel sockets.
279 # Create a context, a session, and the kernel sockets.
278 self.log.info("Starting the kernel at pid: %i", os.getpid())
280 self.log.info("Starting the kernel at pid: %i", os.getpid())
279 context = zmq.Context.instance()
281 context = zmq.Context.instance()
280 # Uncomment this to try closing the context.
282 # Uncomment this to try closing the context.
281 # atexit.register(context.term)
283 # atexit.register(context.term)
282
284
283 self.shell_socket = context.socket(zmq.ROUTER)
285 self.shell_socket = context.socket(zmq.ROUTER)
284 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
286 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
285 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
287 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
286
288
287 self.iopub_socket = context.socket(zmq.PUB)
289 self.iopub_socket = context.socket(zmq.PUB)
288 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
290 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
289 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
291 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
290
292
291 self.stdin_socket = context.socket(zmq.ROUTER)
293 self.stdin_socket = context.socket(zmq.ROUTER)
292 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
294 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
293 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
295 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
296
297 self.control_socket = context.socket(zmq.ROUTER)
298 self.control_port = self._bind_socket(self.control_socket, self.control_port)
299 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
294
300
295 def init_heartbeat(self):
301 def init_heartbeat(self):
296 """start the heart beating"""
302 """start the heart beating"""
297 # heartbeat doesn't share context, because it mustn't be blocked
303 # heartbeat doesn't share context, because it mustn't be blocked
298 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
304 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
299 hb_ctx = zmq.Context()
305 hb_ctx = zmq.Context()
300 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
306 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
301 self.hb_port = self.heartbeat.port
307 self.hb_port = self.heartbeat.port
302 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
308 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
303 self.heartbeat.start()
309 self.heartbeat.start()
304
310
305 # Helper to make it easier to connect to an existing kernel.
311 # Helper to make it easier to connect to an existing kernel.
306 # set log-level to critical, to make sure it is output
312 # set log-level to critical, to make sure it is output
307 self.log.critical("To connect another client to this kernel, use:")
313 self.log.critical("To connect another client to this kernel, use:")
308
314
309 def log_connection_info(self):
315 def log_connection_info(self):
310 """display connection info, and store ports"""
316 """display connection info, and store ports"""
311 basename = os.path.basename(self.connection_file)
317 basename = os.path.basename(self.connection_file)
312 if basename == self.connection_file or \
318 if basename == self.connection_file or \
313 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
319 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
314 # use shortname
320 # use shortname
315 tail = basename
321 tail = basename
316 if self.profile != 'default':
322 if self.profile != 'default':
317 tail += " --profile %s" % self.profile
323 tail += " --profile %s" % self.profile
318 else:
324 else:
319 tail = self.connection_file
325 tail = self.connection_file
320 self.log.critical("--existing %s", tail)
326 self.log.critical("--existing %s", tail)
321
327
322
328
323 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
329 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
324 stdin=self.stdin_port, hb=self.hb_port)
330 stdin=self.stdin_port, hb=self.hb_port,
331 control=self.control_port)
325
332
326 def init_session(self):
333 def init_session(self):
327 """create our session object"""
334 """create our session object"""
328 default_secure(self.config)
335 default_secure(self.config)
329 self.session = Session(config=self.config, username=u'kernel')
336 self.session = Session(config=self.config, username=u'kernel')
330
337
331 def init_blackhole(self):
338 def init_blackhole(self):
332 """redirects stdout/stderr to devnull if necessary"""
339 """redirects stdout/stderr to devnull if necessary"""
333 if self.no_stdout or self.no_stderr:
340 if self.no_stdout or self.no_stderr:
334 blackhole = open(os.devnull, 'w')
341 blackhole = open(os.devnull, 'w')
335 if self.no_stdout:
342 if self.no_stdout:
336 sys.stdout = sys.__stdout__ = blackhole
343 sys.stdout = sys.__stdout__ = blackhole
337 if self.no_stderr:
344 if self.no_stderr:
338 sys.stderr = sys.__stderr__ = blackhole
345 sys.stderr = sys.__stderr__ = blackhole
339
346
340 def init_io(self):
347 def init_io(self):
341 """Redirect input streams and set a display hook."""
348 """Redirect input streams and set a display hook."""
342 if self.outstream_class:
349 if self.outstream_class:
343 outstream_factory = import_item(str(self.outstream_class))
350 outstream_factory = import_item(str(self.outstream_class))
344 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
351 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
345 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
352 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
346 if self.displayhook_class:
353 if self.displayhook_class:
347 displayhook_factory = import_item(str(self.displayhook_class))
354 displayhook_factory = import_item(str(self.displayhook_class))
348 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
355 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
349
356
350 def init_signal(self):
357 def init_signal(self):
351 signal.signal(signal.SIGINT, signal.SIG_IGN)
358 signal.signal(signal.SIGINT, signal.SIG_IGN)
352
359
353 def init_kernel(self):
360 def init_kernel(self):
354 """Create the Kernel object itself"""
361 """Create the Kernel object itself"""
355 shell_stream = ZMQStream(self.shell_socket)
362 shell_stream = ZMQStream(self.shell_socket)
363 control_stream = ZMQStream(self.control_socket)
356
364
357 kernel_factory = import_item(str(self.kernel_class))
365 kernel_factory = import_item(str(self.kernel_class))
358
366
359 kernel = kernel_factory(config=self.config, session=self.session,
367 kernel = kernel_factory(config=self.config, session=self.session,
360 shell_streams=[shell_stream],
368 shell_streams=[shell_stream, control_stream],
361 iopub_socket=self.iopub_socket,
369 iopub_socket=self.iopub_socket,
362 stdin_socket=self.stdin_socket,
370 stdin_socket=self.stdin_socket,
363 log=self.log,
371 log=self.log,
364 profile_dir=self.profile_dir,
372 profile_dir=self.profile_dir,
365 )
373 )
366 kernel.record_ports(self.ports)
374 kernel.record_ports(self.ports)
367 self.kernel = kernel
375 self.kernel = kernel
368
376
369 def init_gui_pylab(self):
377 def init_gui_pylab(self):
370 """Enable GUI event loop integration, taking pylab into account."""
378 """Enable GUI event loop integration, taking pylab into account."""
371
379
372 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
380 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
373 # to ensure that any exception is printed straight to stderr.
381 # to ensure that any exception is printed straight to stderr.
374 # Normally _showtraceback associates the reply with an execution,
382 # Normally _showtraceback associates the reply with an execution,
375 # which means frontends will never draw it, as this exception
383 # which means frontends will never draw it, as this exception
376 # is not associated with any execute request.
384 # is not associated with any execute request.
377
385
378 shell = self.shell
386 shell = self.shell
379 _showtraceback = shell._showtraceback
387 _showtraceback = shell._showtraceback
380 try:
388 try:
381 # replace pyerr-sending traceback with stderr
389 # replace pyerr-sending traceback with stderr
382 def print_tb(etype, evalue, stb):
390 def print_tb(etype, evalue, stb):
383 print ("GUI event loop or pylab initialization failed",
391 print ("GUI event loop or pylab initialization failed",
384 file=io.stderr)
392 file=io.stderr)
385 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
393 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
386 shell._showtraceback = print_tb
394 shell._showtraceback = print_tb
387 InteractiveShellApp.init_gui_pylab(self)
395 InteractiveShellApp.init_gui_pylab(self)
388 finally:
396 finally:
389 shell._showtraceback = _showtraceback
397 shell._showtraceback = _showtraceback
390
398
391 def init_shell(self):
399 def init_shell(self):
392 self.shell = self.kernel.shell
400 self.shell = self.kernel.shell
393 self.shell.configurables.append(self)
401 self.shell.configurables.append(self)
394
402
395 @catch_config_error
403 @catch_config_error
396 def initialize(self, argv=None):
404 def initialize(self, argv=None):
397 super(IPKernelApp, self).initialize(argv)
405 super(IPKernelApp, self).initialize(argv)
398 self.init_blackhole()
406 self.init_blackhole()
399 self.init_connection_file()
407 self.init_connection_file()
400 self.init_session()
408 self.init_session()
401 self.init_poller()
409 self.init_poller()
402 self.init_sockets()
410 self.init_sockets()
403 self.init_heartbeat()
411 self.init_heartbeat()
404 # writing/displaying connection info must be *after* init_sockets/heartbeat
412 # writing/displaying connection info must be *after* init_sockets/heartbeat
405 self.log_connection_info()
413 self.log_connection_info()
406 self.write_connection_file()
414 self.write_connection_file()
407 self.init_io()
415 self.init_io()
408 self.init_signal()
416 self.init_signal()
409 self.init_kernel()
417 self.init_kernel()
410 # shell init steps
418 # shell init steps
411 self.init_path()
419 self.init_path()
412 self.init_shell()
420 self.init_shell()
413 self.init_gui_pylab()
421 self.init_gui_pylab()
414 self.init_extensions()
422 self.init_extensions()
415 self.init_code()
423 self.init_code()
416 # flush stdout/stderr, so that anything written to these streams during
424 # flush stdout/stderr, so that anything written to these streams during
417 # initialization do not get associated with the first execution request
425 # initialization do not get associated with the first execution request
418 sys.stdout.flush()
426 sys.stdout.flush()
419 sys.stderr.flush()
427 sys.stderr.flush()
420
428
421 def start(self):
429 def start(self):
422 if self.poller is not None:
430 if self.poller is not None:
423 self.poller.start()
431 self.poller.start()
424 self.kernel.start()
432 self.kernel.start()
425 try:
433 try:
426 ioloop.IOLoop.instance().start()
434 ioloop.IOLoop.instance().start()
427 except KeyboardInterrupt:
435 except KeyboardInterrupt:
428 pass
436 pass
429
437
430
438
431 def main():
439 def main():
432 """Run an IPKernel as an application"""
440 """Run an IPKernel as an application"""
433 app = IPKernelApp.instance()
441 app = IPKernelApp.instance()
434 app.initialize()
442 app.initialize()
435 app.start()
443 app.start()
436
444
437
445
438 if __name__ == '__main__':
446 if __name__ == '__main__':
439 main()
447 main()
General Comments 0
You need to be logged in to leave comments. Login now