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