##// END OF EJS Templates
use QSocketNotifier, not poll...
MinRK -
Show More
@@ -1,227 +1,255 b''
1 1 # encoding: utf-8
2 2 """Event loop integration for the ZeroMQ-based kernels.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2011 The IPython Development Team
7 7
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import sys
18 18
19 # System library imports.
19 # System library imports
20 20 import zmq
21 21
22 # Local imports.
22 # Local imports
23 23 from IPython.config.application import Application
24 24 from IPython.utils import io
25 25
26 26
27 27 #------------------------------------------------------------------------------
28 28 # Eventloops for integrating the Kernel into different GUIs
29 29 #------------------------------------------------------------------------------
30 30
31 def _on_os_x_10_9():
32 import platform
33 from distutils.version import LooseVersion as V
34 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
35
36 def _notify_stream_qt(kernel, stream):
37
38 from IPython.external.qt_for_kernel import QtCore
39
40 if _on_os_x_10_9() and kernel._darwin_app_nap:
41 from IPython.external.appnope import nope_scope as context
42 else:
43 from IPython.core.interactiveshell import no_op_context as context
44
45 def process_stream_events():
46 while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN:
47 with context():
48 kernel.do_one_iteration()
49
50 fd = stream.getsockopt(zmq.FD)
51 notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app)
52 notifier.activated.connect(process_stream_events)
53
31 54 def loop_qt4(kernel):
32 55 """Start a kernel with PyQt4 event loop integration."""
33 56
34 from IPython.external.qt_for_kernel import QtCore
35 57 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
36 58
37 59 kernel.app = get_app_qt4([" "])
38 60 kernel.app.setQuitOnLastWindowClosed(False)
39 kernel.timer = QtCore.QTimer()
40 kernel.timer.timeout.connect(kernel.do_one_iteration)
41 # Units for the timer are in milliseconds
42 kernel.timer.start(1000*kernel._poll_interval)
61
62 for s in kernel.shell_streams:
63 _notify_stream_qt(kernel, s)
64
43 65 start_event_loop_qt4(kernel.app)
44 66
45 67
46 68 def loop_wx(kernel):
47 69 """Start a kernel with wx event loop support."""
48 70
49 71 import wx
50 72 from IPython.lib.guisupport import start_event_loop_wx
51 73
74 if _on_os_x_10_9() and kernel._darwin_app_nap:
75 # we don't hook up App Nap contexts for Wx,
76 # just disable it outright.
77 from IPython.external.appnope import nope
78 nope()
79
52 80 doi = kernel.do_one_iteration
53 81 # Wx uses milliseconds
54 82 poll_interval = int(1000*kernel._poll_interval)
55 83
56 84 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
57 85 # We make the Frame hidden when we create it in the main app below.
58 86 class TimerFrame(wx.Frame):
59 87 def __init__(self, func):
60 88 wx.Frame.__init__(self, None, -1)
61 89 self.timer = wx.Timer(self)
62 90 # Units for the timer are in milliseconds
63 91 self.timer.Start(poll_interval)
64 92 self.Bind(wx.EVT_TIMER, self.on_timer)
65 93 self.func = func
66 94
67 95 def on_timer(self, event):
68 96 self.func()
69 97
70 98 # We need a custom wx.App to create our Frame subclass that has the
71 99 # wx.Timer to drive the ZMQ event loop.
72 100 class IPWxApp(wx.App):
73 101 def OnInit(self):
74 102 self.frame = TimerFrame(doi)
75 103 self.frame.Show(False)
76 104 return True
77 105
78 106 # The redirect=False here makes sure that wx doesn't replace
79 107 # sys.stdout/stderr with its own classes.
80 108 kernel.app = IPWxApp(redirect=False)
81 109
82 110 # The import of wx on Linux sets the handler for signal.SIGINT
83 111 # to 0. This is a bug in wx or gtk. We fix by just setting it
84 112 # back to the Python default.
85 113 import signal
86 114 if not callable(signal.getsignal(signal.SIGINT)):
87 115 signal.signal(signal.SIGINT, signal.default_int_handler)
88 116
89 117 start_event_loop_wx(kernel.app)
90 118
91 119
92 120 def loop_tk(kernel):
93 121 """Start a kernel with the Tk event loop."""
94 122
95 123 try:
96 124 from tkinter import Tk # Py 3
97 125 except ImportError:
98 126 from Tkinter import Tk # Py 2
99 127 doi = kernel.do_one_iteration
100 128 # Tk uses milliseconds
101 129 poll_interval = int(1000*kernel._poll_interval)
102 130 # For Tkinter, we create a Tk object and call its withdraw method.
103 131 class Timer(object):
104 132 def __init__(self, func):
105 133 self.app = Tk()
106 134 self.app.withdraw()
107 135 self.func = func
108 136
109 137 def on_timer(self):
110 138 self.func()
111 139 self.app.after(poll_interval, self.on_timer)
112 140
113 141 def start(self):
114 142 self.on_timer() # Call it once to get things going.
115 143 self.app.mainloop()
116 144
117 145 kernel.timer = Timer(doi)
118 146 kernel.timer.start()
119 147
120 148
121 149 def loop_gtk(kernel):
122 150 """Start the kernel, coordinating with the GTK event loop"""
123 151 from .gui.gtkembed import GTKEmbed
124 152
125 153 gtk_kernel = GTKEmbed(kernel)
126 154 gtk_kernel.start()
127 155
128 156
129 157 def loop_cocoa(kernel):
130 158 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
131 159 via the matplotlib MacOSX backend.
132 160 """
133 161 import matplotlib
134 162 if matplotlib.__version__ < '1.1.0':
135 163 kernel.log.warn(
136 164 "MacOSX backend in matplotlib %s doesn't have a Timer, "
137 165 "falling back on Tk for CFRunLoop integration. Note that "
138 166 "even this won't work if Tk is linked against X11 instead of "
139 167 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
140 168 "you must use matplotlib >= 1.1.0, or a native libtk."
141 169 )
142 170 return loop_tk(kernel)
143 171
144 172 from matplotlib.backends.backend_macosx import TimerMac, show
145 173
146 174 # scale interval for sec->ms
147 175 poll_interval = int(1000*kernel._poll_interval)
148 176
149 177 real_excepthook = sys.excepthook
150 178 def handle_int(etype, value, tb):
151 179 """don't let KeyboardInterrupts look like crashes"""
152 180 if etype is KeyboardInterrupt:
153 181 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
154 182 else:
155 183 real_excepthook(etype, value, tb)
156 184
157 185 # add doi() as a Timer to the CFRunLoop
158 186 def doi():
159 187 # restore excepthook during IPython code
160 188 sys.excepthook = real_excepthook
161 189 kernel.do_one_iteration()
162 190 # and back:
163 191 sys.excepthook = handle_int
164 192
165 193 t = TimerMac(poll_interval)
166 194 t.add_callback(doi)
167 195 t.start()
168 196
169 197 # but still need a Poller for when there are no active windows,
170 198 # during which time mainloop() returns immediately
171 199 poller = zmq.Poller()
172 200 if kernel.control_stream:
173 201 poller.register(kernel.control_stream.socket, zmq.POLLIN)
174 202 for stream in kernel.shell_streams:
175 203 poller.register(stream.socket, zmq.POLLIN)
176 204
177 205 while True:
178 206 try:
179 207 # double nested try/except, to properly catch KeyboardInterrupt
180 208 # due to pyzmq Issue #130
181 209 try:
182 210 # don't let interrupts during mainloop invoke crash_handler:
183 211 sys.excepthook = handle_int
184 212 show.mainloop()
185 213 sys.excepthook = real_excepthook
186 214 # use poller if mainloop returned (no windows)
187 215 # scale by extra factor of 10, since it's a real poll
188 216 poller.poll(10*poll_interval)
189 217 kernel.do_one_iteration()
190 218 except:
191 219 raise
192 220 except KeyboardInterrupt:
193 221 # Ctrl-C shouldn't crash the kernel
194 222 io.raw_print("KeyboardInterrupt caught in kernel")
195 223 finally:
196 224 # ensure excepthook is restored
197 225 sys.excepthook = real_excepthook
198 226
199 227 # mapping of keys to loop functions
200 228 loop_map = {
201 229 'qt' : loop_qt4,
202 230 'qt4': loop_qt4,
203 231 'inline': None,
204 232 'osx': loop_cocoa,
205 233 'wx' : loop_wx,
206 234 'tk' : loop_tk,
207 235 'gtk': loop_gtk,
208 236 None : None,
209 237 }
210 238
211 239
212 240 def enable_gui(gui, kernel=None):
213 241 """Enable integration with a given GUI"""
214 242 if gui not in loop_map:
215 243 e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys())
216 244 raise ValueError(e)
217 245 if kernel is None:
218 246 if Application.initialized():
219 247 kernel = getattr(Application.instance(), 'kernel', None)
220 248 if kernel is None:
221 249 raise RuntimeError("You didn't specify a kernel,"
222 250 " and no IPython Application with a kernel appears to be running."
223 251 )
224 252 loop = loop_map[gui]
225 253 if loop and kernel.eventloop is not None and kernel.eventloop is not loop:
226 254 raise RuntimeError("Cannot activate multiple GUI eventloops")
227 255 kernel.eventloop = loop
@@ -1,789 +1,795 b''
1 1 #!/usr/bin/env python
2 2 """An interactive kernel that talks to frontends over 0MQ."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Imports
6 6 #-----------------------------------------------------------------------------
7 7 from __future__ import print_function
8 8
9 9 # Standard library imports
10 10 import sys
11 11 import time
12 12 import traceback
13 13 import logging
14 14 import uuid
15 15
16 16 from datetime import datetime
17 17 from signal import (
18 18 signal, default_int_handler, SIGINT
19 19 )
20 20
21 21 # System library imports
22 22 import zmq
23 23 from zmq.eventloop import ioloop
24 24 from zmq.eventloop.zmqstream import ZMQStream
25 25
26 26 # Local imports
27 27 from IPython.config.configurable import Configurable
28 28 from IPython.core.error import StdinNotImplementedError
29 29 from IPython.core import release
30 30 from IPython.utils import py3compat
31 31 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
32 32 from IPython.utils.jsonutil import json_clean
33 33 from IPython.utils.traitlets import (
34 34 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
35 Type
35 Type, Bool,
36 36 )
37 37
38 38 from .serialize import serialize_object, unpack_apply_message
39 39 from .session import Session
40 40 from .zmqshell import ZMQInteractiveShell
41 41
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Main kernel class
45 45 #-----------------------------------------------------------------------------
46 46
47 47 protocol_version = list(release.kernel_protocol_version_info)
48 48 ipython_version = list(release.version_info)
49 49 language_version = list(sys.version_info[:3])
50 50
51 51
52 52 class Kernel(Configurable):
53 53
54 54 #---------------------------------------------------------------------------
55 55 # Kernel interface
56 56 #---------------------------------------------------------------------------
57 57
58 58 # attribute to override with a GUI
59 59 eventloop = Any(None)
60 60 def _eventloop_changed(self, name, old, new):
61 61 """schedule call to eventloop from IOLoop"""
62 62 loop = ioloop.IOLoop.instance()
63 63 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
64 64
65 65 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
66 66 shell_class = Type(ZMQInteractiveShell)
67 67
68 68 session = Instance(Session)
69 69 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
70 70 shell_streams = List()
71 71 control_stream = Instance(ZMQStream)
72 72 iopub_socket = Instance(zmq.Socket)
73 73 stdin_socket = Instance(zmq.Socket)
74 74 log = Instance(logging.Logger)
75 75
76 76 user_module = Any()
77 77 def _user_module_changed(self, name, old, new):
78 78 if self.shell is not None:
79 79 self.shell.user_module = new
80 80
81 81 user_ns = Instance(dict, args=None, allow_none=True)
82 82 def _user_ns_changed(self, name, old, new):
83 83 if self.shell is not None:
84 84 self.shell.user_ns = new
85 85 self.shell.init_user_ns()
86 86
87 87 # identities:
88 88 int_id = Integer(-1)
89 89 ident = Unicode()
90 90
91 91 def _ident_default(self):
92 92 return unicode_type(uuid.uuid4())
93 93
94
95 94 # Private interface
96 95
96 _darwin_app_nap = Bool(True, config=True,
97 help="""Whether to use appnope for compatiblity with OS X App Nap.
98
99 Only affects OS X >= 10.9.
100 """
101 )
102
97 103 # Time to sleep after flushing the stdout/err buffers in each execute
98 104 # cycle. While this introduces a hard limit on the minimal latency of the
99 105 # execute cycle, it helps prevent output synchronization problems for
100 106 # clients.
101 107 # Units are in seconds. The minimum zmq latency on local host is probably
102 108 # ~150 microseconds, set this to 500us for now. We may need to increase it
103 109 # a little if it's not enough after more interactive testing.
104 110 _execute_sleep = Float(0.0005, config=True)
105 111
106 112 # Frequency of the kernel's event loop.
107 113 # Units are in seconds, kernel subclasses for GUI toolkits may need to
108 114 # adapt to milliseconds.
109 115 _poll_interval = Float(0.05, config=True)
110 116
111 117 # If the shutdown was requested over the network, we leave here the
112 118 # necessary reply message so it can be sent by our registered atexit
113 119 # handler. This ensures that the reply is only sent to clients truly at
114 120 # the end of our shutdown process (which happens after the underlying
115 121 # IPython shell's own shutdown).
116 122 _shutdown_message = None
117 123
118 124 # This is a dict of port number that the kernel is listening on. It is set
119 125 # by record_ports and used by connect_request.
120 126 _recorded_ports = Dict()
121 127
122 128 # A reference to the Python builtin 'raw_input' function.
123 129 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
124 130 _sys_raw_input = Any()
125 131 _sys_eval_input = Any()
126 132
127 133 # set of aborted msg_ids
128 134 aborted = Set()
129 135
130 136
131 137 def __init__(self, **kwargs):
132 138 super(Kernel, self).__init__(**kwargs)
133 139
134 140 # Initialize the InteractiveShell subclass
135 141 self.shell = self.shell_class.instance(parent=self,
136 142 profile_dir = self.profile_dir,
137 143 user_module = self.user_module,
138 144 user_ns = self.user_ns,
139 145 kernel = self,
140 146 )
141 147 self.shell.displayhook.session = self.session
142 148 self.shell.displayhook.pub_socket = self.iopub_socket
143 149 self.shell.displayhook.topic = self._topic('pyout')
144 150 self.shell.display_pub.session = self.session
145 151 self.shell.display_pub.pub_socket = self.iopub_socket
146 152 self.shell.data_pub.session = self.session
147 153 self.shell.data_pub.pub_socket = self.iopub_socket
148 154
149 155 # TMP - hack while developing
150 156 self.shell._reply_content = None
151 157
152 158 # Build dict of handlers for message types
153 159 msg_types = [ 'execute_request', 'complete_request',
154 160 'object_info_request', 'history_request',
155 161 'kernel_info_request',
156 162 'connect_request', 'shutdown_request',
157 163 'apply_request',
158 164 ]
159 165 self.shell_handlers = {}
160 166 for msg_type in msg_types:
161 167 self.shell_handlers[msg_type] = getattr(self, msg_type)
162 168
163 169 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
164 170 comm_manager = self.shell.comm_manager
165 171 for msg_type in comm_msg_types:
166 172 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
167 173
168 174 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
169 175 self.control_handlers = {}
170 176 for msg_type in control_msg_types:
171 177 self.control_handlers[msg_type] = getattr(self, msg_type)
172 178
173 179
174 180 def dispatch_control(self, msg):
175 181 """dispatch control requests"""
176 182 idents,msg = self.session.feed_identities(msg, copy=False)
177 183 try:
178 184 msg = self.session.unserialize(msg, content=True, copy=False)
179 185 except:
180 186 self.log.error("Invalid Control Message", exc_info=True)
181 187 return
182 188
183 189 self.log.debug("Control received: %s", msg)
184 190
185 191 header = msg['header']
186 192 msg_id = header['msg_id']
187 193 msg_type = header['msg_type']
188 194
189 195 handler = self.control_handlers.get(msg_type, None)
190 196 if handler is None:
191 197 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
192 198 else:
193 199 try:
194 200 handler(self.control_stream, idents, msg)
195 201 except Exception:
196 202 self.log.error("Exception in control handler:", exc_info=True)
197 203
198 204 def dispatch_shell(self, stream, msg):
199 205 """dispatch shell requests"""
200 206 # flush control requests first
201 207 if self.control_stream:
202 208 self.control_stream.flush()
203 209
204 210 idents,msg = self.session.feed_identities(msg, copy=False)
205 211 try:
206 212 msg = self.session.unserialize(msg, content=True, copy=False)
207 213 except:
208 214 self.log.error("Invalid Message", exc_info=True)
209 215 return
210 216
211 217 header = msg['header']
212 218 msg_id = header['msg_id']
213 219 msg_type = msg['header']['msg_type']
214 220
215 221 # Print some info about this message and leave a '--->' marker, so it's
216 222 # easier to trace visually the message chain when debugging. Each
217 223 # handler prints its message at the end.
218 224 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
219 225 self.log.debug(' Content: %s\n --->\n ', msg['content'])
220 226
221 227 if msg_id in self.aborted:
222 228 self.aborted.remove(msg_id)
223 229 # is it safe to assume a msg_id will not be resubmitted?
224 230 reply_type = msg_type.split('_')[0] + '_reply'
225 231 status = {'status' : 'aborted'}
226 232 md = {'engine' : self.ident}
227 233 md.update(status)
228 234 reply_msg = self.session.send(stream, reply_type, metadata=md,
229 235 content=status, parent=msg, ident=idents)
230 236 return
231 237
232 238 handler = self.shell_handlers.get(msg_type, None)
233 239 if handler is None:
234 240 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
235 241 else:
236 242 # ensure default_int_handler during handler call
237 243 sig = signal(SIGINT, default_int_handler)
238 244 try:
239 245 handler(stream, idents, msg)
240 246 except Exception:
241 247 self.log.error("Exception in message handler:", exc_info=True)
242 248 finally:
243 249 signal(SIGINT, sig)
244 250
245 251 def enter_eventloop(self):
246 252 """enter eventloop"""
247 253 self.log.info("entering eventloop")
248 254 # restore default_int_handler
249 255 signal(SIGINT, default_int_handler)
250 256 while self.eventloop is not None:
251 257 try:
252 258 self.eventloop(self)
253 259 except KeyboardInterrupt:
254 260 # Ctrl-C shouldn't crash the kernel
255 261 self.log.error("KeyboardInterrupt caught in kernel")
256 262 continue
257 263 else:
258 264 # eventloop exited cleanly, this means we should stop (right?)
259 265 self.eventloop = None
260 266 break
261 267 self.log.info("exiting eventloop")
262 268
263 269 def start(self):
264 270 """register dispatchers for streams"""
265 271 self.shell.exit_now = False
266 272 if self.control_stream:
267 273 self.control_stream.on_recv(self.dispatch_control, copy=False)
268 274
269 275 def make_dispatcher(stream):
270 276 def dispatcher(msg):
271 277 return self.dispatch_shell(stream, msg)
272 278 return dispatcher
273 279
274 280 for s in self.shell_streams:
275 281 s.on_recv(make_dispatcher(s), copy=False)
276 282
277 283 # publish idle status
278 284 self._publish_status('starting')
279 285
280 286 def do_one_iteration(self):
281 287 """step eventloop just once"""
282 288 if self.control_stream:
283 289 self.control_stream.flush()
284 290 for stream in self.shell_streams:
285 291 # handle at most one request per iteration
286 292 stream.flush(zmq.POLLIN, 1)
287 293 stream.flush(zmq.POLLOUT)
288 294
289 295
290 296 def record_ports(self, ports):
291 297 """Record the ports that this kernel is using.
292 298
293 299 The creator of the Kernel instance must call this methods if they
294 300 want the :meth:`connect_request` method to return the port numbers.
295 301 """
296 302 self._recorded_ports = ports
297 303
298 304 #---------------------------------------------------------------------------
299 305 # Kernel request handlers
300 306 #---------------------------------------------------------------------------
301 307
302 308 def _make_metadata(self, other=None):
303 309 """init metadata dict, for execute/apply_reply"""
304 310 new_md = {
305 311 'dependencies_met' : True,
306 312 'engine' : self.ident,
307 313 'started': datetime.now(),
308 314 }
309 315 if other:
310 316 new_md.update(other)
311 317 return new_md
312 318
313 319 def _publish_pyin(self, code, parent, execution_count):
314 320 """Publish the code request on the pyin stream."""
315 321
316 322 self.session.send(self.iopub_socket, u'pyin',
317 323 {u'code':code, u'execution_count': execution_count},
318 324 parent=parent, ident=self._topic('pyin')
319 325 )
320 326
321 327 def _publish_status(self, status, parent=None):
322 328 """send status (busy/idle) on IOPub"""
323 329 self.session.send(self.iopub_socket,
324 330 u'status',
325 331 {u'execution_state': status},
326 332 parent=parent,
327 333 ident=self._topic('status'),
328 334 )
329 335
330 336
331 337 def execute_request(self, stream, ident, parent):
332 338 """handle an execute_request"""
333 339
334 340 self._publish_status(u'busy', parent)
335 341
336 342 try:
337 343 content = parent[u'content']
338 344 code = content[u'code']
339 345 silent = content[u'silent']
340 346 store_history = content.get(u'store_history', not silent)
341 347 except:
342 348 self.log.error("Got bad msg: ")
343 349 self.log.error("%s", parent)
344 350 return
345 351
346 352 md = self._make_metadata(parent['metadata'])
347 353
348 354 shell = self.shell # we'll need this a lot here
349 355
350 356 # Replace raw_input. Note that is not sufficient to replace
351 357 # raw_input in the user namespace.
352 358 if content.get('allow_stdin', False):
353 359 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
354 360 input = lambda prompt='': eval(raw_input(prompt))
355 361 else:
356 362 raw_input = input = lambda prompt='' : self._no_raw_input()
357 363
358 364 if py3compat.PY3:
359 365 self._sys_raw_input = builtin_mod.input
360 366 builtin_mod.input = raw_input
361 367 else:
362 368 self._sys_raw_input = builtin_mod.raw_input
363 369 self._sys_eval_input = builtin_mod.input
364 370 builtin_mod.raw_input = raw_input
365 371 builtin_mod.input = input
366 372
367 373 # Set the parent message of the display hook and out streams.
368 374 shell.set_parent(parent)
369 375
370 376 # Re-broadcast our input for the benefit of listening clients, and
371 377 # start computing output
372 378 if not silent:
373 379 self._publish_pyin(code, parent, shell.execution_count)
374 380
375 381 reply_content = {}
376 382 try:
377 383 # FIXME: the shell calls the exception handler itself.
378 384 shell.run_cell(code, store_history=store_history, silent=silent)
379 385 except:
380 386 status = u'error'
381 387 # FIXME: this code right now isn't being used yet by default,
382 388 # because the run_cell() call above directly fires off exception
383 389 # reporting. This code, therefore, is only active in the scenario
384 390 # where runlines itself has an unhandled exception. We need to
385 391 # uniformize this, for all exception construction to come from a
386 392 # single location in the codbase.
387 393 etype, evalue, tb = sys.exc_info()
388 394 tb_list = traceback.format_exception(etype, evalue, tb)
389 395 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
390 396 else:
391 397 status = u'ok'
392 398 finally:
393 399 # Restore raw_input.
394 400 if py3compat.PY3:
395 401 builtin_mod.input = self._sys_raw_input
396 402 else:
397 403 builtin_mod.raw_input = self._sys_raw_input
398 404 builtin_mod.input = self._sys_eval_input
399 405
400 406 reply_content[u'status'] = status
401 407
402 408 # Return the execution counter so clients can display prompts
403 409 reply_content['execution_count'] = shell.execution_count - 1
404 410
405 411 # FIXME - fish exception info out of shell, possibly left there by
406 412 # runlines. We'll need to clean up this logic later.
407 413 if shell._reply_content is not None:
408 414 reply_content.update(shell._reply_content)
409 415 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
410 416 reply_content['engine_info'] = e_info
411 417 # reset after use
412 418 shell._reply_content = None
413 419
414 420 if 'traceback' in reply_content:
415 421 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
416 422
417 423
418 424 # At this point, we can tell whether the main code execution succeeded
419 425 # or not. If it did, we proceed to evaluate user_variables/expressions
420 426 if reply_content['status'] == 'ok':
421 427 reply_content[u'user_variables'] = \
422 428 shell.user_variables(content.get(u'user_variables', []))
423 429 reply_content[u'user_expressions'] = \
424 430 shell.user_expressions(content.get(u'user_expressions', {}))
425 431 else:
426 432 # If there was an error, don't even try to compute variables or
427 433 # expressions
428 434 reply_content[u'user_variables'] = {}
429 435 reply_content[u'user_expressions'] = {}
430 436
431 437 # Payloads should be retrieved regardless of outcome, so we can both
432 438 # recover partial output (that could have been generated early in a
433 439 # block, before an error) and clear the payload system always.
434 440 reply_content[u'payload'] = shell.payload_manager.read_payload()
435 441 # Be agressive about clearing the payload because we don't want
436 442 # it to sit in memory until the next execute_request comes in.
437 443 shell.payload_manager.clear_payload()
438 444
439 445 # Flush output before sending the reply.
440 446 sys.stdout.flush()
441 447 sys.stderr.flush()
442 448 # FIXME: on rare occasions, the flush doesn't seem to make it to the
443 449 # clients... This seems to mitigate the problem, but we definitely need
444 450 # to better understand what's going on.
445 451 if self._execute_sleep:
446 452 time.sleep(self._execute_sleep)
447 453
448 454 # Send the reply.
449 455 reply_content = json_clean(reply_content)
450 456
451 457 md['status'] = reply_content['status']
452 458 if reply_content['status'] == 'error' and \
453 459 reply_content['ename'] == 'UnmetDependency':
454 460 md['dependencies_met'] = False
455 461
456 462 reply_msg = self.session.send(stream, u'execute_reply',
457 463 reply_content, parent, metadata=md,
458 464 ident=ident)
459 465
460 466 self.log.debug("%s", reply_msg)
461 467
462 468 if not silent and reply_msg['content']['status'] == u'error':
463 469 self._abort_queues()
464 470
465 471 self._publish_status(u'idle', parent)
466 472
467 473 def complete_request(self, stream, ident, parent):
468 474 txt, matches = self._complete(parent)
469 475 matches = {'matches' : matches,
470 476 'matched_text' : txt,
471 477 'status' : 'ok'}
472 478 matches = json_clean(matches)
473 479 completion_msg = self.session.send(stream, 'complete_reply',
474 480 matches, parent, ident)
475 481 self.log.debug("%s", completion_msg)
476 482
477 483 def object_info_request(self, stream, ident, parent):
478 484 content = parent['content']
479 485 object_info = self.shell.object_inspect(content['oname'],
480 486 detail_level = content.get('detail_level', 0)
481 487 )
482 488 # Before we send this object over, we scrub it for JSON usage
483 489 oinfo = json_clean(object_info)
484 490 msg = self.session.send(stream, 'object_info_reply',
485 491 oinfo, parent, ident)
486 492 self.log.debug("%s", msg)
487 493
488 494 def history_request(self, stream, ident, parent):
489 495 # We need to pull these out, as passing **kwargs doesn't work with
490 496 # unicode keys before Python 2.6.5.
491 497 hist_access_type = parent['content']['hist_access_type']
492 498 raw = parent['content']['raw']
493 499 output = parent['content']['output']
494 500 if hist_access_type == 'tail':
495 501 n = parent['content']['n']
496 502 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
497 503 include_latest=True)
498 504
499 505 elif hist_access_type == 'range':
500 506 session = parent['content']['session']
501 507 start = parent['content']['start']
502 508 stop = parent['content']['stop']
503 509 hist = self.shell.history_manager.get_range(session, start, stop,
504 510 raw=raw, output=output)
505 511
506 512 elif hist_access_type == 'search':
507 513 n = parent['content'].get('n')
508 514 unique = parent['content'].get('unique', False)
509 515 pattern = parent['content']['pattern']
510 516 hist = self.shell.history_manager.search(
511 517 pattern, raw=raw, output=output, n=n, unique=unique)
512 518
513 519 else:
514 520 hist = []
515 521 hist = list(hist)
516 522 content = {'history' : hist}
517 523 content = json_clean(content)
518 524 msg = self.session.send(stream, 'history_reply',
519 525 content, parent, ident)
520 526 self.log.debug("Sending history reply with %i entries", len(hist))
521 527
522 528 def connect_request(self, stream, ident, parent):
523 529 if self._recorded_ports is not None:
524 530 content = self._recorded_ports.copy()
525 531 else:
526 532 content = {}
527 533 msg = self.session.send(stream, 'connect_reply',
528 534 content, parent, ident)
529 535 self.log.debug("%s", msg)
530 536
531 537 def kernel_info_request(self, stream, ident, parent):
532 538 vinfo = {
533 539 'protocol_version': protocol_version,
534 540 'ipython_version': ipython_version,
535 541 'language_version': language_version,
536 542 'language': 'python',
537 543 }
538 544 msg = self.session.send(stream, 'kernel_info_reply',
539 545 vinfo, parent, ident)
540 546 self.log.debug("%s", msg)
541 547
542 548 def shutdown_request(self, stream, ident, parent):
543 549 self.shell.exit_now = True
544 550 content = dict(status='ok')
545 551 content.update(parent['content'])
546 552 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
547 553 # same content, but different msg_id for broadcasting on IOPub
548 554 self._shutdown_message = self.session.msg(u'shutdown_reply',
549 555 content, parent
550 556 )
551 557
552 558 self._at_shutdown()
553 559 # call sys.exit after a short delay
554 560 loop = ioloop.IOLoop.instance()
555 561 loop.add_timeout(time.time()+0.1, loop.stop)
556 562
557 563 #---------------------------------------------------------------------------
558 564 # Engine methods
559 565 #---------------------------------------------------------------------------
560 566
561 567 def apply_request(self, stream, ident, parent):
562 568 try:
563 569 content = parent[u'content']
564 570 bufs = parent[u'buffers']
565 571 msg_id = parent['header']['msg_id']
566 572 except:
567 573 self.log.error("Got bad msg: %s", parent, exc_info=True)
568 574 return
569 575
570 576 self._publish_status(u'busy', parent)
571 577
572 578 # Set the parent message of the display hook and out streams.
573 579 shell = self.shell
574 580 shell.set_parent(parent)
575 581
576 582 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
577 583 # self.iopub_socket.send(pyin_msg)
578 584 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
579 585 md = self._make_metadata(parent['metadata'])
580 586 try:
581 587 working = shell.user_ns
582 588
583 589 prefix = "_"+str(msg_id).replace("-","")+"_"
584 590
585 591 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
586 592
587 593 fname = getattr(f, '__name__', 'f')
588 594
589 595 fname = prefix+"f"
590 596 argname = prefix+"args"
591 597 kwargname = prefix+"kwargs"
592 598 resultname = prefix+"result"
593 599
594 600 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
595 601 # print ns
596 602 working.update(ns)
597 603 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
598 604 try:
599 605 exec(code, shell.user_global_ns, shell.user_ns)
600 606 result = working.get(resultname)
601 607 finally:
602 608 for key in ns:
603 609 working.pop(key)
604 610
605 611 result_buf = serialize_object(result,
606 612 buffer_threshold=self.session.buffer_threshold,
607 613 item_threshold=self.session.item_threshold,
608 614 )
609 615
610 616 except:
611 617 # invoke IPython traceback formatting
612 618 shell.showtraceback()
613 619 # FIXME - fish exception info out of shell, possibly left there by
614 620 # run_code. We'll need to clean up this logic later.
615 621 reply_content = {}
616 622 if shell._reply_content is not None:
617 623 reply_content.update(shell._reply_content)
618 624 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
619 625 reply_content['engine_info'] = e_info
620 626 # reset after use
621 627 shell._reply_content = None
622 628
623 629 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
624 630 ident=self._topic('pyerr'))
625 631 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
626 632 result_buf = []
627 633
628 634 if reply_content['ename'] == 'UnmetDependency':
629 635 md['dependencies_met'] = False
630 636 else:
631 637 reply_content = {'status' : 'ok'}
632 638
633 639 # put 'ok'/'error' status in header, for scheduler introspection:
634 640 md['status'] = reply_content['status']
635 641
636 642 # flush i/o
637 643 sys.stdout.flush()
638 644 sys.stderr.flush()
639 645
640 646 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
641 647 parent=parent, ident=ident,buffers=result_buf, metadata=md)
642 648
643 649 self._publish_status(u'idle', parent)
644 650
645 651 #---------------------------------------------------------------------------
646 652 # Control messages
647 653 #---------------------------------------------------------------------------
648 654
649 655 def abort_request(self, stream, ident, parent):
650 656 """abort a specifig msg by id"""
651 657 msg_ids = parent['content'].get('msg_ids', None)
652 658 if isinstance(msg_ids, string_types):
653 659 msg_ids = [msg_ids]
654 660 if not msg_ids:
655 661 self.abort_queues()
656 662 for mid in msg_ids:
657 663 self.aborted.add(str(mid))
658 664
659 665 content = dict(status='ok')
660 666 reply_msg = self.session.send(stream, 'abort_reply', content=content,
661 667 parent=parent, ident=ident)
662 668 self.log.debug("%s", reply_msg)
663 669
664 670 def clear_request(self, stream, idents, parent):
665 671 """Clear our namespace."""
666 672 self.shell.reset(False)
667 673 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
668 674 content = dict(status='ok'))
669 675
670 676
671 677 #---------------------------------------------------------------------------
672 678 # Protected interface
673 679 #---------------------------------------------------------------------------
674 680
675 681 def _wrap_exception(self, method=None):
676 682 # import here, because _wrap_exception is only used in parallel,
677 683 # and parallel has higher min pyzmq version
678 684 from IPython.parallel.error import wrap_exception
679 685 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
680 686 content = wrap_exception(e_info)
681 687 return content
682 688
683 689 def _topic(self, topic):
684 690 """prefixed topic for IOPub messages"""
685 691 if self.int_id >= 0:
686 692 base = "engine.%i" % self.int_id
687 693 else:
688 694 base = "kernel.%s" % self.ident
689 695
690 696 return py3compat.cast_bytes("%s.%s" % (base, topic))
691 697
692 698 def _abort_queues(self):
693 699 for stream in self.shell_streams:
694 700 if stream:
695 701 self._abort_queue(stream)
696 702
697 703 def _abort_queue(self, stream):
698 704 poller = zmq.Poller()
699 705 poller.register(stream.socket, zmq.POLLIN)
700 706 while True:
701 707 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
702 708 if msg is None:
703 709 return
704 710
705 711 self.log.info("Aborting:")
706 712 self.log.info("%s", msg)
707 713 msg_type = msg['header']['msg_type']
708 714 reply_type = msg_type.split('_')[0] + '_reply'
709 715
710 716 status = {'status' : 'aborted'}
711 717 md = {'engine' : self.ident}
712 718 md.update(status)
713 719 reply_msg = self.session.send(stream, reply_type, metadata=md,
714 720 content=status, parent=msg, ident=idents)
715 721 self.log.debug("%s", reply_msg)
716 722 # We need to wait a bit for requests to come in. This can probably
717 723 # be set shorter for true asynchronous clients.
718 724 poller.poll(50)
719 725
720 726
721 727 def _no_raw_input(self):
722 728 """Raise StdinNotImplentedError if active frontend doesn't support
723 729 stdin."""
724 730 raise StdinNotImplementedError("raw_input was called, but this "
725 731 "frontend does not support stdin.")
726 732
727 733 def _raw_input(self, prompt, ident, parent):
728 734 # Flush output before making the request.
729 735 sys.stderr.flush()
730 736 sys.stdout.flush()
731 737 # flush the stdin socket, to purge stale replies
732 738 while True:
733 739 try:
734 740 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
735 741 except zmq.ZMQError as e:
736 742 if e.errno == zmq.EAGAIN:
737 743 break
738 744 else:
739 745 raise
740 746
741 747 # Send the input request.
742 748 content = json_clean(dict(prompt=prompt))
743 749 self.session.send(self.stdin_socket, u'input_request', content, parent,
744 750 ident=ident)
745 751
746 752 # Await a response.
747 753 while True:
748 754 try:
749 755 ident, reply = self.session.recv(self.stdin_socket, 0)
750 756 except Exception:
751 757 self.log.warn("Invalid Message:", exc_info=True)
752 758 except KeyboardInterrupt:
753 759 # re-raise KeyboardInterrupt, to truncate traceback
754 760 raise KeyboardInterrupt
755 761 else:
756 762 break
757 763 try:
758 764 value = py3compat.unicode_to_str(reply['content']['value'])
759 765 except:
760 766 self.log.error("Got bad raw_input reply: ")
761 767 self.log.error("%s", parent)
762 768 value = ''
763 769 if value == '\x04':
764 770 # EOF
765 771 raise EOFError
766 772 return value
767 773
768 774 def _complete(self, msg):
769 775 c = msg['content']
770 776 try:
771 777 cpos = int(c['cursor_pos'])
772 778 except:
773 779 # If we don't get something that we can convert to an integer, at
774 780 # least attempt the completion guessing the cursor is at the end of
775 781 # the text, if there's any, and otherwise of the line
776 782 cpos = len(c['text'])
777 783 if cpos==0:
778 784 cpos = len(c['line'])
779 785 return self.shell.complete(c['text'], c['line'], cpos)
780 786
781 787 def _at_shutdown(self):
782 788 """Actions taken at shutdown by the kernel, called by python's atexit.
783 789 """
784 790 # io.rprint("Kernel at_shutdown") # dbg
785 791 if self._shutdown_message is not None:
786 792 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
787 793 self.log.debug("%s", self._shutdown_message)
788 794 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
789 795
@@ -1,480 +1,473 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
161 161 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
162 162 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
163 163 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
164 164 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
165 165 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
166 166 connection_file = Unicode('', config=True,
167 167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168 168
169 169 This file will contain the IP, ports, and authentication key needed to connect
170 170 clients to this kernel. By default, this file will be created in the security dir
171 171 of the current profile, but can be specified by absolute path.
172 172 """)
173 173 @property
174 174 def abs_connection_file(self):
175 175 if os.path.basename(self.connection_file) == self.connection_file:
176 176 return os.path.join(self.profile_dir.security_dir, self.connection_file)
177 177 else:
178 178 return self.connection_file
179 179
180 180
181 181 # streams, etc.
182 182 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
183 183 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
184 184 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
185 185 config=True, help="The importstring for the OutStream factory")
186 186 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
187 187 config=True, help="The importstring for the DisplayHook factory")
188 188
189 189 # polling
190 190 parent_handle = Integer(0, config=True,
191 191 help="""kill this process if its parent dies. On Windows, the argument
192 192 specifies the HANDLE of the parent process, otherwise it is simply boolean.
193 193 """)
194 194 interrupt = Integer(0, config=True,
195 195 help="""ONLY USED ON WINDOWS
196 196 Interrupt this process when the parent is signaled.
197 197 """)
198 198
199 199 def init_crash_handler(self):
200 200 # Install minimal exception handling
201 201 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
202 202 ostream=sys.__stdout__)
203 203
204 204 def init_poller(self):
205 205 if sys.platform == 'win32':
206 206 if self.interrupt or self.parent_handle:
207 207 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
208 208 elif self.parent_handle:
209 209 self.poller = ParentPollerUnix()
210 210
211 211 def _bind_socket(self, s, port):
212 212 iface = '%s://%s' % (self.transport, self.ip)
213 213 if self.transport == 'tcp':
214 214 if port <= 0:
215 215 port = s.bind_to_random_port(iface)
216 216 else:
217 217 s.bind("tcp://%s:%i" % (self.ip, port))
218 218 elif self.transport == 'ipc':
219 219 if port <= 0:
220 220 port = 1
221 221 path = "%s-%i" % (self.ip, port)
222 222 while os.path.exists(path):
223 223 port = port + 1
224 224 path = "%s-%i" % (self.ip, port)
225 225 else:
226 226 path = "%s-%i" % (self.ip, port)
227 227 s.bind("ipc://%s" % path)
228 228 return port
229 229
230 230 def load_connection_file(self):
231 231 """load ip/port/hmac config from JSON connection file"""
232 232 try:
233 233 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
234 234 except IOError:
235 235 self.log.debug("Connection file not found: %s", self.connection_file)
236 236 # This means I own it, so I will clean it up:
237 237 atexit.register(self.cleanup_connection_file)
238 238 return
239 239 self.log.debug(u"Loading connection file %s", fname)
240 240 with open(fname) as f:
241 241 s = f.read()
242 242 cfg = json.loads(s)
243 243 self.transport = cfg.get('transport', self.transport)
244 244 if self.ip == self._ip_default() and 'ip' in cfg:
245 245 # not overridden by config or cl_args
246 246 self.ip = cfg['ip']
247 247 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
248 248 name = channel + '_port'
249 249 if getattr(self, name) == 0 and name in cfg:
250 250 # not overridden by config or cl_args
251 251 setattr(self, name, cfg[name])
252 252 if 'key' in cfg:
253 253 self.config.Session.key = str_to_bytes(cfg['key'])
254 254
255 255 def write_connection_file(self):
256 256 """write connection info to JSON file"""
257 257 cf = self.abs_connection_file
258 258 self.log.debug("Writing connection file: %s", cf)
259 259 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
260 260 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
261 261 iopub_port=self.iopub_port, control_port=self.control_port)
262 262
263 263 def cleanup_connection_file(self):
264 264 cf = self.abs_connection_file
265 265 self.log.debug("Cleaning up connection file: %s", cf)
266 266 try:
267 267 os.remove(cf)
268 268 except (IOError, OSError):
269 269 pass
270 270
271 271 self.cleanup_ipc_files()
272 272
273 273 def cleanup_ipc_files(self):
274 274 """cleanup ipc files if we wrote them"""
275 275 if self.transport != 'ipc':
276 276 return
277 277 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
278 278 ipcfile = "%s-%i" % (self.ip, port)
279 279 try:
280 280 os.remove(ipcfile)
281 281 except (IOError, OSError):
282 282 pass
283 283
284 284 def init_connection_file(self):
285 285 if not self.connection_file:
286 286 self.connection_file = "kernel-%s.json"%os.getpid()
287 287 try:
288 288 self.load_connection_file()
289 289 except Exception:
290 290 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
291 291 self.exit(1)
292 292
293 293 def init_sockets(self):
294 294 # Create a context, a session, and the kernel sockets.
295 295 self.log.info("Starting the kernel at pid: %i", os.getpid())
296 296 context = zmq.Context.instance()
297 297 # Uncomment this to try closing the context.
298 298 # atexit.register(context.term)
299 299
300 300 self.shell_socket = context.socket(zmq.ROUTER)
301 301 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
302 302 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
303 303
304 304 self.iopub_socket = context.socket(zmq.PUB)
305 305 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
306 306 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
307 307
308 308 self.stdin_socket = context.socket(zmq.ROUTER)
309 309 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
310 310 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
311 311
312 312 self.control_socket = context.socket(zmq.ROUTER)
313 313 self.control_port = self._bind_socket(self.control_socket, self.control_port)
314 314 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
315 315
316 316 def init_heartbeat(self):
317 317 """start the heart beating"""
318 318 # heartbeat doesn't share context, because it mustn't be blocked
319 319 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
320 320 hb_ctx = zmq.Context()
321 321 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
322 322 self.hb_port = self.heartbeat.port
323 323 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
324 324 self.heartbeat.start()
325 325
326 326 def log_connection_info(self):
327 327 """display connection info, and store ports"""
328 328 basename = os.path.basename(self.connection_file)
329 329 if basename == self.connection_file or \
330 330 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
331 331 # use shortname
332 332 tail = basename
333 333 if self.profile != 'default':
334 334 tail += " --profile %s" % self.profile
335 335 else:
336 336 tail = self.connection_file
337 337 lines = [
338 338 "To connect another client to this kernel, use:",
339 339 " --existing %s" % tail,
340 340 ]
341 341 # log connection info
342 342 # info-level, so often not shown.
343 343 # frontends should use the %connect_info magic
344 344 # to see the connection info
345 345 for line in lines:
346 346 self.log.info(line)
347 347 # also raw print to the terminal if no parent_handle (`ipython kernel`)
348 348 if not self.parent_handle:
349 349 io.rprint(_ctrl_c_message)
350 350 for line in lines:
351 351 io.rprint(line)
352 352
353 353 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
354 354 stdin=self.stdin_port, hb=self.hb_port,
355 355 control=self.control_port)
356 356
357 357 def init_session(self):
358 358 """create our session object"""
359 359 default_secure(self.config)
360 360 self.session = Session(parent=self, username=u'kernel')
361 361
362 362 def init_blackhole(self):
363 363 """redirects stdout/stderr to devnull if necessary"""
364 364 if self.no_stdout or self.no_stderr:
365 365 blackhole = open(os.devnull, 'w')
366 366 if self.no_stdout:
367 367 sys.stdout = sys.__stdout__ = blackhole
368 368 if self.no_stderr:
369 369 sys.stderr = sys.__stderr__ = blackhole
370 370
371 371 def init_io(self):
372 372 """Redirect input streams and set a display hook."""
373 373 if self.outstream_class:
374 374 outstream_factory = import_item(str(self.outstream_class))
375 375 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
376 376 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
377 377 if self.displayhook_class:
378 378 displayhook_factory = import_item(str(self.displayhook_class))
379 379 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
380 380
381 381 def init_signal(self):
382 382 signal.signal(signal.SIGINT, signal.SIG_IGN)
383 383
384 384 def init_kernel(self):
385 385 """Create the Kernel object itself"""
386 386 shell_stream = ZMQStream(self.shell_socket)
387 387 control_stream = ZMQStream(self.control_socket)
388 388
389 389 kernel_factory = import_item(str(self.kernel_class))
390 390
391 391 kernel = kernel_factory(parent=self, session=self.session,
392 392 shell_streams=[shell_stream, control_stream],
393 393 iopub_socket=self.iopub_socket,
394 394 stdin_socket=self.stdin_socket,
395 395 log=self.log,
396 396 profile_dir=self.profile_dir,
397 397 user_ns=self.user_ns,
398 398 )
399 399 kernel.record_ports(self.ports)
400 400 self.kernel = kernel
401 401
402 402 def init_gui_pylab(self):
403 403 """Enable GUI event loop integration, taking pylab into account."""
404 404
405 405 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
406 406 # to ensure that any exception is printed straight to stderr.
407 407 # Normally _showtraceback associates the reply with an execution,
408 408 # which means frontends will never draw it, as this exception
409 409 # is not associated with any execute request.
410 410
411 411 shell = self.shell
412 412 _showtraceback = shell._showtraceback
413 413 try:
414 414 # replace pyerr-sending traceback with stderr
415 415 def print_tb(etype, evalue, stb):
416 416 print ("GUI event loop or pylab initialization failed",
417 417 file=io.stderr)
418 418 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
419 419 shell._showtraceback = print_tb
420 420 InteractiveShellApp.init_gui_pylab(self)
421 421 finally:
422 422 shell._showtraceback = _showtraceback
423 423
424 424 def init_shell(self):
425 425 self.shell = self.kernel.shell
426 426 self.shell.configurables.append(self)
427 427
428 def init_osx(self):
429 if sys.platform != 'darwin':
430 return
431 from IPython.utils.darwin import disable_app_nap
432 self._activity = disable_app_nap(self.log.warn)
433
434 428 @catch_config_error
435 429 def initialize(self, argv=None):
436 430 super(IPKernelApp, self).initialize(argv)
437 431 self.init_blackhole()
438 432 self.init_connection_file()
439 433 self.init_session()
440 434 self.init_poller()
441 435 self.init_sockets()
442 436 self.init_heartbeat()
443 437 # writing/displaying connection info must be *after* init_sockets/heartbeat
444 438 self.log_connection_info()
445 439 self.write_connection_file()
446 440 self.init_io()
447 441 self.init_signal()
448 self.init_osx()
449 442 self.init_kernel()
450 443 # shell init steps
451 444 self.init_path()
452 445 self.init_shell()
453 446 self.init_gui_pylab()
454 447 self.init_extensions()
455 448 self.init_code()
456 449 # flush stdout/stderr, so that anything written to these streams during
457 450 # initialization do not get associated with the first execution request
458 451 sys.stdout.flush()
459 452 sys.stderr.flush()
460 453
461 454 def start(self):
462 455 if self.poller is not None:
463 456 self.poller.start()
464 457 self.kernel.start()
465 458 try:
466 459 ioloop.IOLoop.instance().start()
467 460 except KeyboardInterrupt:
468 461 pass
469 462
470 463 launch_new_instance = IPKernelApp.launch_instance
471 464
472 465 def main():
473 466 """Run an IPKernel as an application"""
474 467 app = IPKernelApp.instance()
475 468 app.initialize()
476 469 app.start()
477 470
478 471
479 472 if __name__ == '__main__':
480 473 main()
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now