##// END OF EJS Templates
Fix IPython.start_kernel()...
Thomas Kluyver -
Show More
@@ -1,470 +1,471 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 from __future__ import print_function
19 19
20 20 # Standard library imports
21 21 import atexit
22 22 import json
23 23 import os
24 24 import sys
25 25 import signal
26 26
27 27 # System library imports
28 28 import zmq
29 29 from zmq.eventloop import ioloop
30 30 from zmq.eventloop.zmqstream import ZMQStream
31 31
32 32 # IPython imports
33 33 from IPython.core.ultratb import FormattedTB
34 34 from IPython.core.application import (
35 35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
36 36 )
37 37 from IPython.core.profiledir import ProfileDir
38 38 from IPython.core.shellapp import (
39 39 InteractiveShellApp, shell_flags, shell_aliases
40 40 )
41 41 from IPython.utils import io
42 42 from IPython.utils.localinterfaces import LOCALHOST
43 43 from IPython.utils.path import filefind
44 44 from IPython.utils.py3compat import str_to_bytes
45 45 from IPython.utils.traitlets import (
46 46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
47 47 DottedObjectName,
48 48 )
49 49 from IPython.utils.importstring import import_item
50 50 from IPython.kernel import write_connection_file
51 51
52 52 # local imports
53 53 from heartbeat import Heartbeat
54 54 from ipkernel import Kernel
55 55 from parentpoller import ParentPollerUnix, ParentPollerWindows
56 56 from session import (
57 57 Session, session_flags, session_aliases, default_secure,
58 58 )
59 59 from zmqshell import ZMQInteractiveShell
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Flags and Aliases
63 63 #-----------------------------------------------------------------------------
64 64
65 65 kernel_aliases = dict(base_aliases)
66 66 kernel_aliases.update({
67 67 'ip' : 'IPKernelApp.ip',
68 68 'hb' : 'IPKernelApp.hb_port',
69 69 'shell' : 'IPKernelApp.shell_port',
70 70 'iopub' : 'IPKernelApp.iopub_port',
71 71 'stdin' : 'IPKernelApp.stdin_port',
72 72 'control' : 'IPKernelApp.control_port',
73 73 'f' : 'IPKernelApp.connection_file',
74 74 'parent': 'IPKernelApp.parent_handle',
75 75 'transport': 'IPKernelApp.transport',
76 76 })
77 77 if sys.platform.startswith('win'):
78 78 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
79 79
80 80 kernel_flags = dict(base_flags)
81 81 kernel_flags.update({
82 82 'no-stdout' : (
83 83 {'IPKernelApp' : {'no_stdout' : True}},
84 84 "redirect stdout to the null device"),
85 85 'no-stderr' : (
86 86 {'IPKernelApp' : {'no_stderr' : True}},
87 87 "redirect stderr to the null device"),
88 88 'pylab' : (
89 89 {'IPKernelApp' : {'pylab' : 'auto'}},
90 90 """Pre-load matplotlib and numpy for interactive use with
91 91 the default matplotlib backend."""),
92 92 })
93 93
94 94 # inherit flags&aliases for any IPython shell apps
95 95 kernel_aliases.update(shell_aliases)
96 96 kernel_flags.update(shell_flags)
97 97
98 98 # inherit flags&aliases for Sessions
99 99 kernel_aliases.update(session_aliases)
100 100 kernel_flags.update(session_flags)
101 101
102 102 _ctrl_c_message = """\
103 103 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
104 104
105 105 To exit, you will have to explicitly quit this process, by either sending
106 106 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
107 107
108 108 To read more about this, see https://github.com/ipython/ipython/issues/2049
109 109
110 110 """
111 111
112 112 #-----------------------------------------------------------------------------
113 113 # Application class for starting an IPython Kernel
114 114 #-----------------------------------------------------------------------------
115 115
116 116 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
117 117 name='ipkernel'
118 118 aliases = Dict(kernel_aliases)
119 119 flags = Dict(kernel_flags)
120 120 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
121 121 # the kernel class, as an importstring
122 122 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
123 123 help="""The Kernel subclass to be used.
124 124
125 125 This should allow easy re-use of the IPKernelApp entry point
126 126 to configure and launch kernels other than IPython's own.
127 127 """)
128 128 kernel = Any()
129 129 poller = Any() # don't restrict this even though current pollers are all Threads
130 130 heartbeat = Instance(Heartbeat)
131 131 session = Instance('IPython.kernel.zmq.session.Session')
132 132 ports = Dict()
133 133
134 134 # ipkernel doesn't get its own config file
135 135 def _config_file_name_default(self):
136 136 return 'ipython_config.py'
137 137
138 138 # inherit config file name from parent:
139 139 parent_appname = Unicode(config=True)
140 140 def _parent_appname_changed(self, name, old, new):
141 141 if self.config_file_specified:
142 142 # it was manually specified, ignore
143 143 return
144 144 self.config_file_name = new.replace('-','_') + u'_config.py'
145 145 # don't let this count as specifying the config file
146 146 self.config_file_specified.remove(self.config_file_name)
147 147
148 148 # connection info:
149 149 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
150 150 ip = Unicode(config=True,
151 151 help="Set the IP or interface on which the kernel will listen.")
152 152 def _ip_default(self):
153 153 if self.transport == 'ipc':
154 154 if self.connection_file:
155 155 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
156 156 else:
157 157 return 'kernel-ipc'
158 158 else:
159 159 return LOCALHOST
160 160 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
161 161 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
162 162 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
163 163 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
164 164 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
165 165 connection_file = Unicode('', config=True,
166 166 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
167 167
168 168 This file will contain the IP, ports, and authentication key needed to connect
169 169 clients to this kernel. By default, this file will be created in the security dir
170 170 of the current profile, but can be specified by absolute path.
171 171 """)
172 172 @property
173 173 def abs_connection_file(self):
174 174 if os.path.basename(self.connection_file) == self.connection_file:
175 175 return os.path.join(self.profile_dir.security_dir, self.connection_file)
176 176 else:
177 177 return self.connection_file
178 178
179 179
180 180 # streams, etc.
181 181 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
182 182 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
183 183 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
184 184 config=True, help="The importstring for the OutStream factory")
185 185 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
186 186 config=True, help="The importstring for the DisplayHook factory")
187 187
188 188 # polling
189 189 parent_handle = Integer(0, config=True,
190 190 help="""kill this process if its parent dies. On Windows, the argument
191 191 specifies the HANDLE of the parent process, otherwise it is simply boolean.
192 192 """)
193 193 interrupt = Integer(0, config=True,
194 194 help="""ONLY USED ON WINDOWS
195 195 Interrupt this process when the parent is signaled.
196 196 """)
197 197
198 198 def init_crash_handler(self):
199 199 # Install minimal exception handling
200 200 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
201 201 ostream=sys.__stdout__)
202 202
203 203 def init_poller(self):
204 204 if sys.platform == 'win32':
205 205 if self.interrupt or self.parent_handle:
206 206 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
207 207 elif self.parent_handle:
208 208 self.poller = ParentPollerUnix()
209 209
210 210 def _bind_socket(self, s, port):
211 211 iface = '%s://%s' % (self.transport, self.ip)
212 212 if self.transport == 'tcp':
213 213 if port <= 0:
214 214 port = s.bind_to_random_port(iface)
215 215 else:
216 216 s.bind("tcp://%s:%i" % (self.ip, port))
217 217 elif self.transport == 'ipc':
218 218 if port <= 0:
219 219 port = 1
220 220 path = "%s-%i" % (self.ip, port)
221 221 while os.path.exists(path):
222 222 port = port + 1
223 223 path = "%s-%i" % (self.ip, port)
224 224 else:
225 225 path = "%s-%i" % (self.ip, port)
226 226 s.bind("ipc://%s" % path)
227 227 return port
228 228
229 229 def load_connection_file(self):
230 230 """load ip/port/hmac config from JSON connection file"""
231 231 try:
232 232 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
233 233 except IOError:
234 234 self.log.debug("Connection file not found: %s", self.connection_file)
235 235 # This means I own it, so I will clean it up:
236 236 atexit.register(self.cleanup_connection_file)
237 237 return
238 238 self.log.debug(u"Loading connection file %s", fname)
239 239 with open(fname) as f:
240 240 s = f.read()
241 241 cfg = json.loads(s)
242 242 self.transport = cfg.get('transport', self.transport)
243 243 if self.ip == self._ip_default() and 'ip' in cfg:
244 244 # not overridden by config or cl_args
245 245 self.ip = cfg['ip']
246 246 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
247 247 name = channel + '_port'
248 248 if getattr(self, name) == 0 and name in cfg:
249 249 # not overridden by config or cl_args
250 250 setattr(self, name, cfg[name])
251 251 if 'key' in cfg:
252 252 self.config.Session.key = str_to_bytes(cfg['key'])
253 253
254 254 def write_connection_file(self):
255 255 """write connection info to JSON file"""
256 256 cf = self.abs_connection_file
257 257 self.log.debug("Writing connection file: %s", cf)
258 258 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
259 259 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
260 260 iopub_port=self.iopub_port, control_port=self.control_port)
261 261
262 262 def cleanup_connection_file(self):
263 263 cf = self.abs_connection_file
264 264 self.log.debug("Cleaning up connection file: %s", cf)
265 265 try:
266 266 os.remove(cf)
267 267 except (IOError, OSError):
268 268 pass
269 269
270 270 self.cleanup_ipc_files()
271 271
272 272 def cleanup_ipc_files(self):
273 273 """cleanup ipc files if we wrote them"""
274 274 if self.transport != 'ipc':
275 275 return
276 276 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
277 277 ipcfile = "%s-%i" % (self.ip, port)
278 278 try:
279 279 os.remove(ipcfile)
280 280 except (IOError, OSError):
281 281 pass
282 282
283 283 def init_connection_file(self):
284 284 if not self.connection_file:
285 285 self.connection_file = "kernel-%s.json"%os.getpid()
286 286 try:
287 287 self.load_connection_file()
288 288 except Exception:
289 289 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
290 290 self.exit(1)
291 291
292 292 def init_sockets(self):
293 293 # Create a context, a session, and the kernel sockets.
294 294 self.log.info("Starting the kernel at pid: %i", os.getpid())
295 295 context = zmq.Context.instance()
296 296 # Uncomment this to try closing the context.
297 297 # atexit.register(context.term)
298 298
299 299 self.shell_socket = context.socket(zmq.ROUTER)
300 300 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
301 301 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
302 302
303 303 self.iopub_socket = context.socket(zmq.PUB)
304 304 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
305 305 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
306 306
307 307 self.stdin_socket = context.socket(zmq.ROUTER)
308 308 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
309 309 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
310 310
311 311 self.control_socket = context.socket(zmq.ROUTER)
312 312 self.control_port = self._bind_socket(self.control_socket, self.control_port)
313 313 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
314 314
315 315 def init_heartbeat(self):
316 316 """start the heart beating"""
317 317 # heartbeat doesn't share context, because it mustn't be blocked
318 318 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
319 319 hb_ctx = zmq.Context()
320 320 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
321 321 self.hb_port = self.heartbeat.port
322 322 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
323 323 self.heartbeat.start()
324 324
325 325 def log_connection_info(self):
326 326 """display connection info, and store ports"""
327 327 basename = os.path.basename(self.connection_file)
328 328 if basename == self.connection_file or \
329 329 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
330 330 # use shortname
331 331 tail = basename
332 332 if self.profile != 'default':
333 333 tail += " --profile %s" % self.profile
334 334 else:
335 335 tail = self.connection_file
336 336 lines = [
337 337 "To connect another client to this kernel, use:",
338 338 " --existing %s" % tail,
339 339 ]
340 340 # log connection info
341 341 # info-level, so often not shown.
342 342 # frontends should use the %connect_info magic
343 343 # to see the connection info
344 344 for line in lines:
345 345 self.log.info(line)
346 346 # also raw print to the terminal if no parent_handle (`ipython kernel`)
347 347 if not self.parent_handle:
348 348 io.rprint(_ctrl_c_message)
349 349 for line in lines:
350 350 io.rprint(line)
351 351
352 352 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
353 353 stdin=self.stdin_port, hb=self.hb_port,
354 354 control=self.control_port)
355 355
356 356 def init_session(self):
357 357 """create our session object"""
358 358 default_secure(self.config)
359 359 self.session = Session(parent=self, username=u'kernel')
360 360
361 361 def init_blackhole(self):
362 362 """redirects stdout/stderr to devnull if necessary"""
363 363 if self.no_stdout or self.no_stderr:
364 364 blackhole = open(os.devnull, 'w')
365 365 if self.no_stdout:
366 366 sys.stdout = sys.__stdout__ = blackhole
367 367 if self.no_stderr:
368 368 sys.stderr = sys.__stderr__ = blackhole
369 369
370 370 def init_io(self):
371 371 """Redirect input streams and set a display hook."""
372 372 if self.outstream_class:
373 373 outstream_factory = import_item(str(self.outstream_class))
374 374 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
375 375 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
376 376 if self.displayhook_class:
377 377 displayhook_factory = import_item(str(self.displayhook_class))
378 378 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
379 379
380 380 def init_signal(self):
381 381 signal.signal(signal.SIGINT, signal.SIG_IGN)
382 382
383 383 def init_kernel(self):
384 384 """Create the Kernel object itself"""
385 385 shell_stream = ZMQStream(self.shell_socket)
386 386 control_stream = ZMQStream(self.control_socket)
387 387
388 388 kernel_factory = import_item(str(self.kernel_class))
389 389
390 390 kernel = kernel_factory(parent=self, session=self.session,
391 391 shell_streams=[shell_stream, control_stream],
392 392 iopub_socket=self.iopub_socket,
393 393 stdin_socket=self.stdin_socket,
394 394 log=self.log,
395 395 profile_dir=self.profile_dir,
396 396 )
397 397 kernel.record_ports(self.ports)
398 398 self.kernel = kernel
399 399
400 400 def init_gui_pylab(self):
401 401 """Enable GUI event loop integration, taking pylab into account."""
402 402
403 403 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
404 404 # to ensure that any exception is printed straight to stderr.
405 405 # Normally _showtraceback associates the reply with an execution,
406 406 # which means frontends will never draw it, as this exception
407 407 # is not associated with any execute request.
408 408
409 409 shell = self.shell
410 410 _showtraceback = shell._showtraceback
411 411 try:
412 412 # replace pyerr-sending traceback with stderr
413 413 def print_tb(etype, evalue, stb):
414 414 print ("GUI event loop or pylab initialization failed",
415 415 file=io.stderr)
416 416 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
417 417 shell._showtraceback = print_tb
418 418 InteractiveShellApp.init_gui_pylab(self)
419 419 finally:
420 420 shell._showtraceback = _showtraceback
421 421
422 422 def init_shell(self):
423 423 self.shell = self.kernel.shell
424 424 self.shell.configurables.append(self)
425 425
426 426 @catch_config_error
427 427 def initialize(self, argv=None):
428 428 super(IPKernelApp, self).initialize(argv)
429 429 self.init_blackhole()
430 430 self.init_connection_file()
431 431 self.init_session()
432 432 self.init_poller()
433 433 self.init_sockets()
434 434 self.init_heartbeat()
435 435 # writing/displaying connection info must be *after* init_sockets/heartbeat
436 436 self.log_connection_info()
437 437 self.write_connection_file()
438 438 self.init_io()
439 439 self.init_signal()
440 440 self.init_kernel()
441 441 # shell init steps
442 442 self.init_path()
443 443 self.init_shell()
444 444 self.init_gui_pylab()
445 445 self.init_extensions()
446 446 self.init_code()
447 447 # flush stdout/stderr, so that anything written to these streams during
448 448 # initialization do not get associated with the first execution request
449 449 sys.stdout.flush()
450 450 sys.stderr.flush()
451 451
452 452 def start(self):
453 453 if self.poller is not None:
454 454 self.poller.start()
455 455 self.kernel.start()
456 456 try:
457 457 ioloop.IOLoop.instance().start()
458 458 except KeyboardInterrupt:
459 459 pass
460 460
461 launch_new_instance = IPKernelApp.launch_instance
461 462
462 463 def main():
463 464 """Run an IPKernel as an application"""
464 465 app = IPKernelApp.instance()
465 466 app.initialize()
466 467 app.start()
467 468
468 469
469 470 if __name__ == '__main__':
470 471 main()
General Comments 0
You need to be logged in to leave comments. Login now