##// END OF EJS Templates
Merge remote-tracking branch 'takluyver/history-request' into takluyver-history-request
Thomas Kluyver -
r3843:54f4e0bf merge
parent child Browse files
Show More
@@ -1,496 +1,496
1 1 """ A FrontendWidget that emulates the interface of the console IPython and
2 2 supports the additional functionality provided by the IPython kernel.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Imports
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # Standard library imports
10 10 from collections import namedtuple
11 11 import os.path
12 12 import re
13 13 from subprocess import Popen
14 14 import sys
15 15 from textwrap import dedent
16 16
17 17 # System library imports
18 18 from IPython.external.qt import QtCore, QtGui
19 19
20 20 # Local imports
21 21 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 22 transform_ipy_prompt
23 23 from IPython.core.usage import default_gui_banner
24 24 from IPython.utils.traitlets import Bool, Str, Unicode
25 25 from frontend_widget import FrontendWidget
26 26 from styles import (default_light_style_sheet, default_light_syntax_style,
27 27 default_dark_style_sheet, default_dark_syntax_style,
28 28 default_bw_style_sheet, default_bw_syntax_style)
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Constants
32 32 #-----------------------------------------------------------------------------
33 33
34 34 # Default strings to build and display input and output prompts (and separators
35 35 # in between)
36 36 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
37 37 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
38 38 default_input_sep = '\n'
39 39 default_output_sep = ''
40 40 default_output_sep2 = ''
41 41
42 42 # Base path for most payload sources.
43 43 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # IPythonWidget class
47 47 #-----------------------------------------------------------------------------
48 48
49 49 class IPythonWidget(FrontendWidget):
50 50 """ A FrontendWidget for an IPython kernel.
51 51 """
52 52
53 53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 55 # settings.
56 56 custom_edit = Bool(False)
57 57 custom_edit_requested = QtCore.Signal(object, object)
58 58
59 59 # A command for invoking a system text editor. If the string contains a
60 60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 61 # be appended to the end the command.
62 62 editor = Unicode('default', config=True)
63 63
64 64 # The editor command to use when a specific line number is requested. The
65 65 # string should contain two format specifiers: {line} and {filename}. If
66 66 # this parameter is not specified, the line number option to the %edit magic
67 67 # will be ignored.
68 68 editor_line = Unicode(config=True)
69 69
70 70 # A CSS stylesheet. The stylesheet can contain classes for:
71 71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 74 style_sheet = Unicode(config=True)
75 75
76 76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 77 # the style sheet is queried for Pygments style information.
78 78 syntax_style = Str(config=True)
79 79
80 80 # Prompts.
81 81 in_prompt = Str(default_in_prompt, config=True)
82 82 out_prompt = Str(default_out_prompt, config=True)
83 83 input_sep = Str(default_input_sep, config=True)
84 84 output_sep = Str(default_output_sep, config=True)
85 85 output_sep2 = Str(default_output_sep2, config=True)
86 86
87 87 # FrontendWidget protected class variables.
88 88 _input_splitter_class = IPythonInputSplitter
89 89
90 90 # IPythonWidget protected class variables.
91 91 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
92 92 _payload_source_edit = zmq_shell_source + '.edit_magic'
93 93 _payload_source_exit = zmq_shell_source + '.ask_exit'
94 94 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
95 95 _payload_source_page = 'IPython.zmq.page.page'
96 96
97 97 #---------------------------------------------------------------------------
98 98 # 'object' interface
99 99 #---------------------------------------------------------------------------
100 100
101 101 def __init__(self, *args, **kw):
102 102 super(IPythonWidget, self).__init__(*args, **kw)
103 103
104 104 # IPythonWidget protected variables.
105 105 self._code_to_load = None
106 106 self._payload_handlers = {
107 107 self._payload_source_edit : self._handle_payload_edit,
108 108 self._payload_source_exit : self._handle_payload_exit,
109 109 self._payload_source_page : self._handle_payload_page,
110 110 self._payload_source_loadpy : self._handle_payload_loadpy }
111 111 self._previous_prompt_obj = None
112 112 self._keep_kernel_on_exit = None
113 113
114 114 # Initialize widget styling.
115 115 if self.style_sheet:
116 116 self._style_sheet_changed()
117 117 self._syntax_style_changed()
118 118 else:
119 119 self.set_default_style()
120 120
121 121 #---------------------------------------------------------------------------
122 122 # 'BaseFrontendMixin' abstract interface
123 123 #---------------------------------------------------------------------------
124 124
125 125 def _handle_complete_reply(self, rep):
126 126 """ Reimplemented to support IPython's improved completion machinery.
127 127 """
128 128 cursor = self._get_cursor()
129 129 info = self._request_info.get('complete')
130 130 if info and info.id == rep['parent_header']['msg_id'] and \
131 131 info.pos == cursor.position():
132 132 matches = rep['content']['matches']
133 133 text = rep['content']['matched_text']
134 134 offset = len(text)
135 135
136 136 # Clean up matches with period and path separators if the matched
137 137 # text has not been transformed. This is done by truncating all
138 138 # but the last component and then suitably decreasing the offset
139 139 # between the current cursor position and the start of completion.
140 140 if len(matches) > 1 and matches[0][:offset] == text:
141 141 parts = re.split(r'[./\\]', text)
142 142 sep_count = len(parts) - 1
143 143 if sep_count:
144 144 chop_length = sum(map(len, parts[:sep_count])) + sep_count
145 145 matches = [ match[chop_length:] for match in matches ]
146 146 offset -= chop_length
147 147
148 148 # Move the cursor to the start of the match and complete.
149 149 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
150 150 self._complete_with_items(cursor, matches)
151 151
152 152 def _handle_execute_reply(self, msg):
153 153 """ Reimplemented to support prompt requests.
154 154 """
155 155 info = self._request_info.get('execute')
156 156 if info and info.id == msg['parent_header']['msg_id']:
157 157 if info.kind == 'prompt':
158 158 number = msg['content']['execution_count'] + 1
159 159 self._show_interpreter_prompt(number)
160 160 else:
161 161 super(IPythonWidget, self)._handle_execute_reply(msg)
162 162
163 def _handle_history_tail_reply(self, msg):
163 def _handle_history_reply(self, msg):
164 164 """ Implemented to handle history tail replies, which are only supported
165 165 by the IPython kernel.
166 166 """
167 167 history_items = msg['content']['history']
168 168 items = [ line.rstrip() for _, _, line in history_items ]
169 169 self._set_history(items)
170 170
171 171 def _handle_pyout(self, msg):
172 172 """ Reimplemented for IPython-style "display hook".
173 173 """
174 174 if not self._hidden and self._is_from_this_session(msg):
175 175 content = msg['content']
176 176 prompt_number = content['execution_count']
177 177 data = content['data']
178 178 if data.has_key('text/html'):
179 179 self._append_plain_text(self.output_sep)
180 180 self._append_html(self._make_out_prompt(prompt_number))
181 181 html = data['text/html']
182 182 self._append_plain_text('\n')
183 183 self._append_html(html + self.output_sep2)
184 184 elif data.has_key('text/plain'):
185 185 self._append_plain_text(self.output_sep)
186 186 self._append_html(self._make_out_prompt(prompt_number))
187 187 text = data['text/plain']
188 188 self._append_plain_text(text + self.output_sep2)
189 189
190 190 def _handle_display_data(self, msg):
191 191 """ The base handler for the ``display_data`` message.
192 192 """
193 193 # For now, we don't display data from other frontends, but we
194 194 # eventually will as this allows all frontends to monitor the display
195 195 # data. But we need to figure out how to handle this in the GUI.
196 196 if not self._hidden and self._is_from_this_session(msg):
197 197 source = msg['content']['source']
198 198 data = msg['content']['data']
199 199 metadata = msg['content']['metadata']
200 200 # In the regular IPythonWidget, we simply print the plain text
201 201 # representation.
202 202 if data.has_key('text/html'):
203 203 html = data['text/html']
204 204 self._append_html(html)
205 205 elif data.has_key('text/plain'):
206 206 text = data['text/plain']
207 207 self._append_plain_text(text)
208 208 # This newline seems to be needed for text and html output.
209 209 self._append_plain_text(u'\n')
210 210
211 211 def _started_channels(self):
212 212 """ Reimplemented to make a history request.
213 213 """
214 214 super(IPythonWidget, self)._started_channels()
215 self.kernel_manager.xreq_channel.history_tail(1000)
215 self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
216 216
217 217 #---------------------------------------------------------------------------
218 218 # 'ConsoleWidget' public interface
219 219 #---------------------------------------------------------------------------
220 220
221 221 def copy(self):
222 222 """ Copy the currently selected text to the clipboard, removing prompts
223 223 if possible.
224 224 """
225 225 text = self._control.textCursor().selection().toPlainText()
226 226 if text:
227 227 lines = map(transform_ipy_prompt, text.splitlines())
228 228 text = '\n'.join(lines)
229 229 QtGui.QApplication.clipboard().setText(text)
230 230
231 231 #---------------------------------------------------------------------------
232 232 # 'FrontendWidget' public interface
233 233 #---------------------------------------------------------------------------
234 234
235 235 def execute_file(self, path, hidden=False):
236 236 """ Reimplemented to use the 'run' magic.
237 237 """
238 238 # Use forward slashes on Windows to avoid escaping each separator.
239 239 if sys.platform == 'win32':
240 240 path = os.path.normpath(path).replace('\\', '/')
241 241
242 242 self.execute('%%run %s' % path, hidden=hidden)
243 243
244 244 #---------------------------------------------------------------------------
245 245 # 'FrontendWidget' protected interface
246 246 #---------------------------------------------------------------------------
247 247
248 248 def _complete(self):
249 249 """ Reimplemented to support IPython's improved completion machinery.
250 250 """
251 251 # We let the kernel split the input line, so we *always* send an empty
252 252 # text field. Readline-based frontends do get a real text field which
253 253 # they can use.
254 254 text = ''
255 255
256 256 # Send the completion request to the kernel
257 257 msg_id = self.kernel_manager.xreq_channel.complete(
258 258 text, # text
259 259 self._get_input_buffer_cursor_line(), # line
260 260 self._get_input_buffer_cursor_column(), # cursor_pos
261 261 self.input_buffer) # block
262 262 pos = self._get_cursor().position()
263 263 info = self._CompletionRequest(msg_id, pos)
264 264 self._request_info['complete'] = info
265 265
266 266 def _get_banner(self):
267 267 """ Reimplemented to return IPython's default banner.
268 268 """
269 269 return default_gui_banner
270 270
271 271 def _process_execute_error(self, msg):
272 272 """ Reimplemented for IPython-style traceback formatting.
273 273 """
274 274 content = msg['content']
275 275 traceback = '\n'.join(content['traceback']) + '\n'
276 276 if False:
277 277 # FIXME: For now, tracebacks come as plain text, so we can't use
278 278 # the html renderer yet. Once we refactor ultratb to produce
279 279 # properly styled tracebacks, this branch should be the default
280 280 traceback = traceback.replace(' ', '&nbsp;')
281 281 traceback = traceback.replace('\n', '<br/>')
282 282
283 283 ename = content['ename']
284 284 ename_styled = '<span class="error">%s</span>' % ename
285 285 traceback = traceback.replace(ename, ename_styled)
286 286
287 287 self._append_html(traceback)
288 288 else:
289 289 # This is the fallback for now, using plain text with ansi escapes
290 290 self._append_plain_text(traceback)
291 291
292 292 def _process_execute_payload(self, item):
293 293 """ Reimplemented to dispatch payloads to handler methods.
294 294 """
295 295 handler = self._payload_handlers.get(item['source'])
296 296 if handler is None:
297 297 # We have no handler for this type of payload, simply ignore it
298 298 return False
299 299 else:
300 300 handler(item)
301 301 return True
302 302
303 303 def _show_interpreter_prompt(self, number=None):
304 304 """ Reimplemented for IPython-style prompts.
305 305 """
306 306 # If a number was not specified, make a prompt number request.
307 307 if number is None:
308 308 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
309 309 info = self._ExecutionRequest(msg_id, 'prompt')
310 310 self._request_info['execute'] = info
311 311 return
312 312
313 313 # Show a new prompt and save information about it so that it can be
314 314 # updated later if the prompt number turns out to be wrong.
315 315 self._prompt_sep = self.input_sep
316 316 self._show_prompt(self._make_in_prompt(number), html=True)
317 317 block = self._control.document().lastBlock()
318 318 length = len(self._prompt)
319 319 self._previous_prompt_obj = self._PromptBlock(block, length, number)
320 320
321 321 # Update continuation prompt to reflect (possibly) new prompt length.
322 322 self._set_continuation_prompt(
323 323 self._make_continuation_prompt(self._prompt), html=True)
324 324
325 325 # Load code from the %loadpy magic, if necessary.
326 326 if self._code_to_load is not None:
327 327 self.input_buffer = dedent(self._code_to_load.rstrip())
328 328 self._code_to_load = None
329 329
330 330 def _show_interpreter_prompt_for_reply(self, msg):
331 331 """ Reimplemented for IPython-style prompts.
332 332 """
333 333 # Update the old prompt number if necessary.
334 334 content = msg['content']
335 335 previous_prompt_number = content['execution_count']
336 336 if self._previous_prompt_obj and \
337 337 self._previous_prompt_obj.number != previous_prompt_number:
338 338 block = self._previous_prompt_obj.block
339 339
340 340 # Make sure the prompt block has not been erased.
341 341 if block.isValid() and block.text():
342 342
343 343 # Remove the old prompt and insert a new prompt.
344 344 cursor = QtGui.QTextCursor(block)
345 345 cursor.movePosition(QtGui.QTextCursor.Right,
346 346 QtGui.QTextCursor.KeepAnchor,
347 347 self._previous_prompt_obj.length)
348 348 prompt = self._make_in_prompt(previous_prompt_number)
349 349 self._prompt = self._insert_html_fetching_plain_text(
350 350 cursor, prompt)
351 351
352 352 # When the HTML is inserted, Qt blows away the syntax
353 353 # highlighting for the line, so we need to rehighlight it.
354 354 self._highlighter.rehighlightBlock(cursor.block())
355 355
356 356 self._previous_prompt_obj = None
357 357
358 358 # Show a new prompt with the kernel's estimated prompt number.
359 359 self._show_interpreter_prompt(previous_prompt_number + 1)
360 360
361 361 #---------------------------------------------------------------------------
362 362 # 'IPythonWidget' interface
363 363 #---------------------------------------------------------------------------
364 364
365 365 def set_default_style(self, colors='lightbg'):
366 366 """ Sets the widget style to the class defaults.
367 367
368 368 Parameters:
369 369 -----------
370 370 colors : str, optional (default lightbg)
371 371 Whether to use the default IPython light background or dark
372 372 background or B&W style.
373 373 """
374 374 colors = colors.lower()
375 375 if colors=='lightbg':
376 376 self.style_sheet = default_light_style_sheet
377 377 self.syntax_style = default_light_syntax_style
378 378 elif colors=='linux':
379 379 self.style_sheet = default_dark_style_sheet
380 380 self.syntax_style = default_dark_syntax_style
381 381 elif colors=='nocolor':
382 382 self.style_sheet = default_bw_style_sheet
383 383 self.syntax_style = default_bw_syntax_style
384 384 else:
385 385 raise KeyError("No such color scheme: %s"%colors)
386 386
387 387 #---------------------------------------------------------------------------
388 388 # 'IPythonWidget' protected interface
389 389 #---------------------------------------------------------------------------
390 390
391 391 def _edit(self, filename, line=None):
392 392 """ Opens a Python script for editing.
393 393
394 394 Parameters:
395 395 -----------
396 396 filename : str
397 397 A path to a local system file.
398 398
399 399 line : int, optional
400 400 A line of interest in the file.
401 401 """
402 402 if self.custom_edit:
403 403 self.custom_edit_requested.emit(filename, line)
404 404 elif self.editor == 'default':
405 405 self._append_plain_text('No default editor available.\n')
406 406 else:
407 407 try:
408 408 filename = '"%s"' % filename
409 409 if line and self.editor_line:
410 410 command = self.editor_line.format(filename=filename,
411 411 line=line)
412 412 else:
413 413 try:
414 414 command = self.editor.format()
415 415 except KeyError:
416 416 command = self.editor.format(filename=filename)
417 417 else:
418 418 command += ' ' + filename
419 419 except KeyError:
420 420 self._append_plain_text('Invalid editor command.\n')
421 421 else:
422 422 try:
423 423 Popen(command, shell=True)
424 424 except OSError:
425 425 msg = 'Opening editor with command "%s" failed.\n'
426 426 self._append_plain_text(msg % command)
427 427
428 428 def _make_in_prompt(self, number):
429 429 """ Given a prompt number, returns an HTML In prompt.
430 430 """
431 431 body = self.in_prompt % number
432 432 return '<span class="in-prompt">%s</span>' % body
433 433
434 434 def _make_continuation_prompt(self, prompt):
435 435 """ Given a plain text version of an In prompt, returns an HTML
436 436 continuation prompt.
437 437 """
438 438 end_chars = '...: '
439 439 space_count = len(prompt.lstrip('\n')) - len(end_chars)
440 440 body = '&nbsp;' * space_count + end_chars
441 441 return '<span class="in-prompt">%s</span>' % body
442 442
443 443 def _make_out_prompt(self, number):
444 444 """ Given a prompt number, returns an HTML Out prompt.
445 445 """
446 446 body = self.out_prompt % number
447 447 return '<span class="out-prompt">%s</span>' % body
448 448
449 449 #------ Payload handlers --------------------------------------------------
450 450
451 451 # Payload handlers with a generic interface: each takes the opaque payload
452 452 # dict, unpacks it and calls the underlying functions with the necessary
453 453 # arguments.
454 454
455 455 def _handle_payload_edit(self, item):
456 456 self._edit(item['filename'], item['line_number'])
457 457
458 458 def _handle_payload_exit(self, item):
459 459 self._keep_kernel_on_exit = item['keepkernel']
460 460 self.exit_requested.emit()
461 461
462 462 def _handle_payload_loadpy(self, item):
463 463 # Simple save the text of the .py file for later. The text is written
464 464 # to the buffer when _prompt_started_hook is called.
465 465 self._code_to_load = item['text']
466 466
467 467 def _handle_payload_page(self, item):
468 468 # Since the plain text widget supports only a very small subset of HTML
469 469 # and we have no control over the HTML source, we only page HTML
470 470 # payloads in the rich text widget.
471 471 if item['html'] and self.kind == 'rich':
472 472 self._page(item['html'], html=True)
473 473 else:
474 474 self._page(item['text'], html=False)
475 475
476 476 #------ Trait change handlers --------------------------------------------
477 477
478 478 def _style_sheet_changed(self):
479 479 """ Set the style sheets of the underlying widgets.
480 480 """
481 481 self.setStyleSheet(self.style_sheet)
482 482 self._control.document().setDefaultStyleSheet(self.style_sheet)
483 483 if self._page_control:
484 484 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
485 485
486 486 bg_color = self._control.palette().window().color()
487 487 self._ansi_processor.set_background_color(bg_color)
488 488
489 489 def _syntax_style_changed(self):
490 490 """ Set the style for the syntax highlighter.
491 491 """
492 492 if self.syntax_style:
493 493 self._highlighter.set_style(self.syntax_style)
494 494 else:
495 495 self._highlighter.set_style_sheet(self.style_sheet)
496 496
@@ -1,243 +1,243
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # System library imports.
5 5 from IPython.external.qt import QtCore
6 6
7 7 # IPython imports.
8 8 from IPython.utils.traitlets import Type
9 9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
11 11 from util import MetaQObjectHasTraits, SuperQObject
12 12
13 13
14 14 class SocketChannelQObject(SuperQObject):
15 15
16 16 # Emitted when the channel is started.
17 17 started = QtCore.Signal()
18 18
19 19 # Emitted when the channel is stopped.
20 20 stopped = QtCore.Signal()
21 21
22 22 #---------------------------------------------------------------------------
23 23 # 'ZmqSocketChannel' interface
24 24 #---------------------------------------------------------------------------
25 25
26 26 def start(self):
27 27 """ Reimplemented to emit signal.
28 28 """
29 29 super(SocketChannelQObject, self).start()
30 30 self.started.emit()
31 31
32 32 def stop(self):
33 33 """ Reimplemented to emit signal.
34 34 """
35 35 super(SocketChannelQObject, self).stop()
36 36 self.stopped.emit()
37 37
38 38
39 39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
40 40
41 41 # Emitted when any message is received.
42 42 message_received = QtCore.Signal(object)
43 43
44 44 # Emitted when a reply has been received for the corresponding request
45 45 # type.
46 46 execute_reply = QtCore.Signal(object)
47 47 complete_reply = QtCore.Signal(object)
48 48 object_info_reply = QtCore.Signal(object)
49 history_tail_reply = QtCore.Signal(object)
49 history_reply = QtCore.Signal(object)
50 50
51 51 # Emitted when the first reply comes back.
52 52 first_reply = QtCore.Signal()
53 53
54 54 # Used by the first_reply signal logic to determine if a reply is the
55 55 # first.
56 56 _handlers_called = False
57 57
58 58 #---------------------------------------------------------------------------
59 59 # 'XReqSocketChannel' interface
60 60 #---------------------------------------------------------------------------
61 61
62 62 def call_handlers(self, msg):
63 63 """ Reimplemented to emit signals instead of making callbacks.
64 64 """
65 65 # Emit the generic signal.
66 66 self.message_received.emit(msg)
67 67
68 68 # Emit signals for specialized message types.
69 69 msg_type = msg['msg_type']
70 70 signal = getattr(self, msg_type, None)
71 71 if signal:
72 72 signal.emit(msg)
73 73
74 74 if not self._handlers_called:
75 75 self.first_reply.emit()
76 76 self._handlers_called = True
77 77
78 78 #---------------------------------------------------------------------------
79 79 # 'QtXReqSocketChannel' interface
80 80 #---------------------------------------------------------------------------
81 81
82 82 def reset_first_reply(self):
83 83 """ Reset the first_reply signal to fire again on the next reply.
84 84 """
85 85 self._handlers_called = False
86 86
87 87
88 88 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
89 89
90 90 # Emitted when any message is received.
91 91 message_received = QtCore.Signal(object)
92 92
93 93 # Emitted when a message of type 'stream' is received.
94 94 stream_received = QtCore.Signal(object)
95 95
96 96 # Emitted when a message of type 'pyin' is received.
97 97 pyin_received = QtCore.Signal(object)
98 98
99 99 # Emitted when a message of type 'pyout' is received.
100 100 pyout_received = QtCore.Signal(object)
101 101
102 102 # Emitted when a message of type 'pyerr' is received.
103 103 pyerr_received = QtCore.Signal(object)
104 104
105 105 # Emitted when a message of type 'display_data' is received
106 106 display_data_received = QtCore.Signal(object)
107 107
108 108 # Emitted when a crash report message is received from the kernel's
109 109 # last-resort sys.excepthook.
110 110 crash_received = QtCore.Signal(object)
111 111
112 112 # Emitted when a shutdown is noticed.
113 113 shutdown_reply_received = QtCore.Signal(object)
114 114
115 115 #---------------------------------------------------------------------------
116 116 # 'SubSocketChannel' interface
117 117 #---------------------------------------------------------------------------
118 118
119 119 def call_handlers(self, msg):
120 120 """ Reimplemented to emit signals instead of making callbacks.
121 121 """
122 122 # Emit the generic signal.
123 123 self.message_received.emit(msg)
124 124 # Emit signals for specialized message types.
125 125 msg_type = msg['msg_type']
126 126 signal = getattr(self, msg_type + '_received', None)
127 127 if signal:
128 128 signal.emit(msg)
129 129 elif msg_type in ('stdout', 'stderr'):
130 130 self.stream_received.emit(msg)
131 131
132 132 def flush(self):
133 133 """ Reimplemented to ensure that signals are dispatched immediately.
134 134 """
135 135 super(QtSubSocketChannel, self).flush()
136 136 QtCore.QCoreApplication.instance().processEvents()
137 137
138 138
139 139 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
140 140
141 141 # Emitted when any message is received.
142 142 message_received = QtCore.Signal(object)
143 143
144 144 # Emitted when an input request is received.
145 145 input_requested = QtCore.Signal(object)
146 146
147 147 #---------------------------------------------------------------------------
148 148 # 'RepSocketChannel' interface
149 149 #---------------------------------------------------------------------------
150 150
151 151 def call_handlers(self, msg):
152 152 """ Reimplemented to emit signals instead of making callbacks.
153 153 """
154 154 # Emit the generic signal.
155 155 self.message_received.emit(msg)
156 156
157 157 # Emit signals for specialized message types.
158 158 msg_type = msg['msg_type']
159 159 if msg_type == 'input_request':
160 160 self.input_requested.emit(msg)
161 161
162 162
163 163 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
164 164
165 165 # Emitted when the kernel has died.
166 166 kernel_died = QtCore.Signal(object)
167 167
168 168 #---------------------------------------------------------------------------
169 169 # 'HBSocketChannel' interface
170 170 #---------------------------------------------------------------------------
171 171
172 172 def call_handlers(self, since_last_heartbeat):
173 173 """ Reimplemented to emit signals instead of making callbacks.
174 174 """
175 175 # Emit the generic signal.
176 176 self.kernel_died.emit(since_last_heartbeat)
177 177
178 178
179 179 class QtKernelManager(KernelManager, SuperQObject):
180 180 """ A KernelManager that provides signals and slots.
181 181 """
182 182
183 183 __metaclass__ = MetaQObjectHasTraits
184 184
185 185 # Emitted when the kernel manager has started listening.
186 186 started_channels = QtCore.Signal()
187 187
188 188 # Emitted when the kernel manager has stopped listening.
189 189 stopped_channels = QtCore.Signal()
190 190
191 191 # Use Qt-specific channel classes that emit signals.
192 192 sub_channel_class = Type(QtSubSocketChannel)
193 193 xreq_channel_class = Type(QtXReqSocketChannel)
194 194 rep_channel_class = Type(QtRepSocketChannel)
195 195 hb_channel_class = Type(QtHBSocketChannel)
196 196
197 197 #---------------------------------------------------------------------------
198 198 # 'KernelManager' interface
199 199 #---------------------------------------------------------------------------
200 200
201 201 #------ Kernel process management ------------------------------------------
202 202
203 203 def start_kernel(self, *args, **kw):
204 204 """ Reimplemented for proper heartbeat management.
205 205 """
206 206 if self._xreq_channel is not None:
207 207 self._xreq_channel.reset_first_reply()
208 208 super(QtKernelManager, self).start_kernel(*args, **kw)
209 209
210 210 #------ Channel management -------------------------------------------------
211 211
212 212 def start_channels(self, *args, **kw):
213 213 """ Reimplemented to emit signal.
214 214 """
215 215 super(QtKernelManager, self).start_channels(*args, **kw)
216 216 self.started_channels.emit()
217 217
218 218 def stop_channels(self):
219 219 """ Reimplemented to emit signal.
220 220 """
221 221 super(QtKernelManager, self).stop_channels()
222 222 self.stopped_channels.emit()
223 223
224 224 @property
225 225 def xreq_channel(self):
226 226 """ Reimplemented for proper heartbeat management.
227 227 """
228 228 if self._xreq_channel is None:
229 229 self._xreq_channel = super(QtKernelManager, self).xreq_channel
230 230 self._xreq_channel.first_reply.connect(self._first_reply)
231 231 return self._xreq_channel
232 232
233 233 #---------------------------------------------------------------------------
234 234 # Protected interface
235 235 #---------------------------------------------------------------------------
236 236
237 237 def _first_reply(self):
238 238 """ Unpauses the heartbeat channel when the first reply is received on
239 239 the execute channel. Note that this will *not* start the heartbeat
240 240 channel if it is not already running!
241 241 """
242 242 if self._hb_channel is not None:
243 243 self._hb_channel.unpause()
@@ -1,662 +1,679
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports.
19 19 import __builtin__
20 20 import atexit
21 21 import sys
22 22 import time
23 23 import traceback
24 24 import logging
25 25 # System library imports.
26 26 import zmq
27 27
28 28 # Local imports.
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.utils import io
31 31 from IPython.utils.jsonutil import json_clean
32 32 from IPython.lib import pylabtools
33 33 from IPython.utils.traitlets import Instance, Float
34 34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 35 start_kernel)
36 36 from iostream import OutStream
37 37 from session import Session, Message
38 38 from zmqshell import ZMQInteractiveShell
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Globals
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Module-level logger
45 45 logger = logging.getLogger(__name__)
46 46
47 47 # FIXME: this needs to be done more cleanly later, once we have proper
48 48 # configuration support. This is a library, so it shouldn't set a stream
49 49 # handler, see:
50 50 # http://docs.python.org/library/logging.html#configuring-logging-for-a-library
51 51 # But this lets us at least do developer debugging for now by manually turning
52 52 # it on/off. And once we have full config support, the client entry points
53 53 # will select their logging handlers, as well as passing to this library the
54 54 # logging level.
55 55
56 56 if 0: # dbg - set to 1 to actually see the messages.
57 57 logger.addHandler(logging.StreamHandler())
58 58 logger.setLevel(logging.DEBUG)
59 59
60 60 # /FIXME
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Main kernel class
64 64 #-----------------------------------------------------------------------------
65 65
66 66 class Kernel(Configurable):
67 67
68 68 #---------------------------------------------------------------------------
69 69 # Kernel interface
70 70 #---------------------------------------------------------------------------
71 71
72 72 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
73 73 session = Instance(Session)
74 74 reply_socket = Instance('zmq.Socket')
75 75 pub_socket = Instance('zmq.Socket')
76 76 req_socket = Instance('zmq.Socket')
77 77
78 78 # Private interface
79 79
80 80 # Time to sleep after flushing the stdout/err buffers in each execute
81 81 # cycle. While this introduces a hard limit on the minimal latency of the
82 82 # execute cycle, it helps prevent output synchronization problems for
83 83 # clients.
84 84 # Units are in seconds. The minimum zmq latency on local host is probably
85 85 # ~150 microseconds, set this to 500us for now. We may need to increase it
86 86 # a little if it's not enough after more interactive testing.
87 87 _execute_sleep = Float(0.0005, config=True)
88 88
89 89 # Frequency of the kernel's event loop.
90 90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
91 91 # adapt to milliseconds.
92 92 _poll_interval = Float(0.05, config=True)
93 93
94 94 # If the shutdown was requested over the network, we leave here the
95 95 # necessary reply message so it can be sent by our registered atexit
96 96 # handler. This ensures that the reply is only sent to clients truly at
97 97 # the end of our shutdown process (which happens after the underlying
98 98 # IPython shell's own shutdown).
99 99 _shutdown_message = None
100 100
101 101 # This is a dict of port number that the kernel is listening on. It is set
102 102 # by record_ports and used by connect_request.
103 103 _recorded_ports = None
104 104
105 105
106 106 def __init__(self, **kwargs):
107 107 super(Kernel, self).__init__(**kwargs)
108 108
109 109 # Before we even start up the shell, register *first* our exit handlers
110 110 # so they come before the shell's
111 111 atexit.register(self._at_shutdown)
112 112
113 113 # Initialize the InteractiveShell subclass
114 114 self.shell = ZMQInteractiveShell.instance()
115 115 self.shell.displayhook.session = self.session
116 116 self.shell.displayhook.pub_socket = self.pub_socket
117 117 self.shell.display_pub.session = self.session
118 118 self.shell.display_pub.pub_socket = self.pub_socket
119 119
120 120 # TMP - hack while developing
121 121 self.shell._reply_content = None
122 122
123 123 # Build dict of handlers for message types
124 124 msg_types = [ 'execute_request', 'complete_request',
125 'object_info_request', 'history_tail_request',
125 'object_info_request', 'history_request',
126 126 'connect_request', 'shutdown_request']
127 127 self.handlers = {}
128 128 for msg_type in msg_types:
129 129 self.handlers[msg_type] = getattr(self, msg_type)
130 130
131 131 def do_one_iteration(self):
132 132 """Do one iteration of the kernel's evaluation loop.
133 133 """
134 134 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
135 135 if msg is None:
136 136 return
137 137
138 138 # This assert will raise in versions of zeromq 2.0.7 and lesser.
139 139 # We now require 2.0.8 or above, so we can uncomment for safety.
140 140 # print(ident,msg, file=sys.__stdout__)
141 141 assert ident is not None, "Missing message part."
142 142
143 143 # Print some info about this message and leave a '--->' marker, so it's
144 144 # easier to trace visually the message chain when debugging. Each
145 145 # handler prints its message at the end.
146 146 # Eventually we'll move these from stdout to a logger.
147 147 logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
148 148 logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
149 149
150 150 # Find and call actual handler for message
151 151 handler = self.handlers.get(msg['msg_type'], None)
152 152 if handler is None:
153 153 logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
154 154 else:
155 155 handler(ident, msg)
156 156
157 157 # Check whether we should exit, in case the incoming message set the
158 158 # exit flag on
159 159 if self.shell.exit_now:
160 160 logger.debug('\nExiting IPython kernel...')
161 161 # We do a normal, clean exit, which allows any actions registered
162 162 # via atexit (such as history saving) to take place.
163 163 sys.exit(0)
164 164
165 165
166 166 def start(self):
167 167 """ Start the kernel main loop.
168 168 """
169 169 while True:
170 170 time.sleep(self._poll_interval)
171 171 self.do_one_iteration()
172 172
173 173 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
174 174 """Record the ports that this kernel is using.
175 175
176 176 The creator of the Kernel instance must call this methods if they
177 177 want the :meth:`connect_request` method to return the port numbers.
178 178 """
179 179 self._recorded_ports = {
180 180 'xrep_port' : xrep_port,
181 181 'pub_port' : pub_port,
182 182 'req_port' : req_port,
183 183 'hb_port' : hb_port
184 184 }
185 185
186 186 #---------------------------------------------------------------------------
187 187 # Kernel request handlers
188 188 #---------------------------------------------------------------------------
189 189
190 190 def _publish_pyin(self, code, parent):
191 191 """Publish the code request on the pyin stream."""
192 192
193 193 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
194 194
195 195 def execute_request(self, ident, parent):
196 196
197 197 status_msg = self.session.send(self.pub_socket,
198 198 u'status',
199 199 {u'execution_state':u'busy'},
200 200 parent=parent
201 201 )
202 202
203 203 try:
204 204 content = parent[u'content']
205 205 code = content[u'code']
206 206 silent = content[u'silent']
207 207 except:
208 208 logger.error("Got bad msg: ")
209 209 logger.error(str(Message(parent)))
210 210 return
211 211
212 212 shell = self.shell # we'll need this a lot here
213 213
214 214 # Replace raw_input. Note that is not sufficient to replace
215 215 # raw_input in the user namespace.
216 216 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
217 217 __builtin__.raw_input = raw_input
218 218
219 219 # Set the parent message of the display hook and out streams.
220 220 shell.displayhook.set_parent(parent)
221 221 shell.display_pub.set_parent(parent)
222 222 sys.stdout.set_parent(parent)
223 223 sys.stderr.set_parent(parent)
224 224
225 225 # Re-broadcast our input for the benefit of listening clients, and
226 226 # start computing output
227 227 if not silent:
228 228 self._publish_pyin(code, parent)
229 229
230 230 reply_content = {}
231 231 try:
232 232 if silent:
233 233 # run_code uses 'exec' mode, so no displayhook will fire, and it
234 234 # doesn't call logging or history manipulations. Print
235 235 # statements in that code will obviously still execute.
236 236 shell.run_code(code)
237 237 else:
238 238 # FIXME: the shell calls the exception handler itself.
239 239 shell._reply_content = None
240 240 shell.run_cell(code)
241 241 except:
242 242 status = u'error'
243 243 # FIXME: this code right now isn't being used yet by default,
244 244 # because the run_cell() call above directly fires off exception
245 245 # reporting. This code, therefore, is only active in the scenario
246 246 # where runlines itself has an unhandled exception. We need to
247 247 # uniformize this, for all exception construction to come from a
248 248 # single location in the codbase.
249 249 etype, evalue, tb = sys.exc_info()
250 250 tb_list = traceback.format_exception(etype, evalue, tb)
251 251 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
252 252 else:
253 253 status = u'ok'
254 254
255 255 reply_content[u'status'] = status
256 256
257 257 # Return the execution counter so clients can display prompts
258 258 reply_content['execution_count'] = shell.execution_count -1
259 259
260 260 # FIXME - fish exception info out of shell, possibly left there by
261 261 # runlines. We'll need to clean up this logic later.
262 262 if shell._reply_content is not None:
263 263 reply_content.update(shell._reply_content)
264 264
265 265 # At this point, we can tell whether the main code execution succeeded
266 266 # or not. If it did, we proceed to evaluate user_variables/expressions
267 267 if reply_content['status'] == 'ok':
268 268 reply_content[u'user_variables'] = \
269 269 shell.user_variables(content[u'user_variables'])
270 270 reply_content[u'user_expressions'] = \
271 271 shell.user_expressions(content[u'user_expressions'])
272 272 else:
273 273 # If there was an error, don't even try to compute variables or
274 274 # expressions
275 275 reply_content[u'user_variables'] = {}
276 276 reply_content[u'user_expressions'] = {}
277 277
278 278 # Payloads should be retrieved regardless of outcome, so we can both
279 279 # recover partial output (that could have been generated early in a
280 280 # block, before an error) and clear the payload system always.
281 281 reply_content[u'payload'] = shell.payload_manager.read_payload()
282 282 # Be agressive about clearing the payload because we don't want
283 283 # it to sit in memory until the next execute_request comes in.
284 284 shell.payload_manager.clear_payload()
285 285
286 286 # Flush output before sending the reply.
287 287 sys.stdout.flush()
288 288 sys.stderr.flush()
289 289 # FIXME: on rare occasions, the flush doesn't seem to make it to the
290 290 # clients... This seems to mitigate the problem, but we definitely need
291 291 # to better understand what's going on.
292 292 if self._execute_sleep:
293 293 time.sleep(self._execute_sleep)
294 294
295 295 # Send the reply.
296 296 reply_msg = self.session.send(self.reply_socket, u'execute_reply',
297 297 reply_content, parent, ident=ident)
298 298 logger.debug(str(reply_msg))
299 299
300 300 if reply_msg['content']['status'] == u'error':
301 301 self._abort_queue()
302 302
303 303 status_msg = self.session.send(self.pub_socket,
304 304 u'status',
305 305 {u'execution_state':u'idle'},
306 306 parent=parent
307 307 )
308 308
309 309 def complete_request(self, ident, parent):
310 310 txt, matches = self._complete(parent)
311 311 matches = {'matches' : matches,
312 312 'matched_text' : txt,
313 313 'status' : 'ok'}
314 314 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
315 315 matches, parent, ident)
316 316 logger.debug(str(completion_msg))
317 317
318 318 def object_info_request(self, ident, parent):
319 319 object_info = self.shell.object_inspect(parent['content']['oname'])
320 320 # Before we send this object over, we scrub it for JSON usage
321 321 oinfo = json_clean(object_info)
322 322 msg = self.session.send(self.reply_socket, 'object_info_reply',
323 323 oinfo, parent, ident)
324 324 logger.debug(msg)
325 325
326 def history_tail_request(self, ident, parent):
326 def history_request(self, ident, parent):
327 327 # We need to pull these out, as passing **kwargs doesn't work with
328 328 # unicode keys before Python 2.6.5.
329 n = parent['content']['n']
329 hist_access_type = parent['content']['hist_access_type']
330 330 raw = parent['content']['raw']
331 331 output = parent['content']['output']
332 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output)
332 if hist_access_type == 'tail':
333 n = parent['content']['n']
334 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
335 include_latest=True)
336
337 elif hist_access_type == 'range':
338 session = parent['content']['session']
339 start = parent['content']['start']
340 stop = parent['content']['stop']
341 hist = self.shell.history_manager.get_range(session, start, stop,
342 raw=raw, output=output)
343
344 elif hist_access_type == 'search':
345 pattern = parent['content']['pattern']
346 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
347
348 else:
349 hist = []
333 350 content = {'history' : list(hist)}
334 msg = self.session.send(self.reply_socket, 'history_tail_reply',
351 msg = self.session.send(self.reply_socket, 'history_reply',
335 352 content, parent, ident)
336 353 logger.debug(str(msg))
337 354
338 355 def connect_request(self, ident, parent):
339 356 if self._recorded_ports is not None:
340 357 content = self._recorded_ports.copy()
341 358 else:
342 359 content = {}
343 360 msg = self.session.send(self.reply_socket, 'connect_reply',
344 361 content, parent, ident)
345 362 logger.debug(msg)
346 363
347 364 def shutdown_request(self, ident, parent):
348 365 self.shell.exit_now = True
349 366 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
350 367 sys.exit(0)
351 368
352 369 #---------------------------------------------------------------------------
353 370 # Protected interface
354 371 #---------------------------------------------------------------------------
355 372
356 373 def _abort_queue(self):
357 374 while True:
358 375 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
359 376 if msg is None:
360 377 break
361 378 else:
362 379 assert ident is not None, \
363 380 "Unexpected missing message part."
364 381
365 382 logger.debug("Aborting:\n"+str(Message(msg)))
366 383 msg_type = msg['msg_type']
367 384 reply_type = msg_type.split('_')[0] + '_reply'
368 385 reply_msg = self.session.send(self.reply_socket, reply_type,
369 386 {'status' : 'aborted'}, msg, ident=ident)
370 387 logger.debug(reply_msg)
371 388 # We need to wait a bit for requests to come in. This can probably
372 389 # be set shorter for true asynchronous clients.
373 390 time.sleep(0.1)
374 391
375 392 def _raw_input(self, prompt, ident, parent):
376 393 # Flush output before making the request.
377 394 sys.stderr.flush()
378 395 sys.stdout.flush()
379 396
380 397 # Send the input request.
381 398 content = dict(prompt=prompt)
382 399 msg = self.session.send(self.req_socket, u'input_request', content, parent)
383 400
384 401 # Await a response.
385 402 ident, reply = self.session.recv(self.req_socket, 0)
386 403 try:
387 404 value = reply['content']['value']
388 405 except:
389 406 logger.error("Got bad raw_input reply: ")
390 407 logger.error(str(Message(parent)))
391 408 value = ''
392 409 return value
393 410
394 411 def _complete(self, msg):
395 412 c = msg['content']
396 413 try:
397 414 cpos = int(c['cursor_pos'])
398 415 except:
399 416 # If we don't get something that we can convert to an integer, at
400 417 # least attempt the completion guessing the cursor is at the end of
401 418 # the text, if there's any, and otherwise of the line
402 419 cpos = len(c['text'])
403 420 if cpos==0:
404 421 cpos = len(c['line'])
405 422 return self.shell.complete(c['text'], c['line'], cpos)
406 423
407 424 def _object_info(self, context):
408 425 symbol, leftover = self._symbol_from_context(context)
409 426 if symbol is not None and not leftover:
410 427 doc = getattr(symbol, '__doc__', '')
411 428 else:
412 429 doc = ''
413 430 object_info = dict(docstring = doc)
414 431 return object_info
415 432
416 433 def _symbol_from_context(self, context):
417 434 if not context:
418 435 return None, context
419 436
420 437 base_symbol_string = context[0]
421 438 symbol = self.shell.user_ns.get(base_symbol_string, None)
422 439 if symbol is None:
423 440 symbol = __builtin__.__dict__.get(base_symbol_string, None)
424 441 if symbol is None:
425 442 return None, context
426 443
427 444 context = context[1:]
428 445 for i, name in enumerate(context):
429 446 new_symbol = getattr(symbol, name, None)
430 447 if new_symbol is None:
431 448 return symbol, context[i:]
432 449 else:
433 450 symbol = new_symbol
434 451
435 452 return symbol, []
436 453
437 454 def _at_shutdown(self):
438 455 """Actions taken at shutdown by the kernel, called by python's atexit.
439 456 """
440 457 # io.rprint("Kernel at_shutdown") # dbg
441 458 if self._shutdown_message is not None:
442 459 self.session.send(self.reply_socket, self._shutdown_message)
443 460 self.session.send(self.pub_socket, self._shutdown_message)
444 461 logger.debug(str(self._shutdown_message))
445 462 # A very short sleep to give zmq time to flush its message buffers
446 463 # before Python truly shuts down.
447 464 time.sleep(0.01)
448 465
449 466
450 467 class QtKernel(Kernel):
451 468 """A Kernel subclass with Qt support."""
452 469
453 470 def start(self):
454 471 """Start a kernel with QtPy4 event loop integration."""
455 472
456 473 from PyQt4 import QtCore
457 474 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
458 475
459 476 self.app = get_app_qt4([" "])
460 477 self.app.setQuitOnLastWindowClosed(False)
461 478 self.timer = QtCore.QTimer()
462 479 self.timer.timeout.connect(self.do_one_iteration)
463 480 # Units for the timer are in milliseconds
464 481 self.timer.start(1000*self._poll_interval)
465 482 start_event_loop_qt4(self.app)
466 483
467 484
468 485 class WxKernel(Kernel):
469 486 """A Kernel subclass with Wx support."""
470 487
471 488 def start(self):
472 489 """Start a kernel with wx event loop support."""
473 490
474 491 import wx
475 492 from IPython.lib.guisupport import start_event_loop_wx
476 493
477 494 doi = self.do_one_iteration
478 495 # Wx uses milliseconds
479 496 poll_interval = int(1000*self._poll_interval)
480 497
481 498 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
482 499 # We make the Frame hidden when we create it in the main app below.
483 500 class TimerFrame(wx.Frame):
484 501 def __init__(self, func):
485 502 wx.Frame.__init__(self, None, -1)
486 503 self.timer = wx.Timer(self)
487 504 # Units for the timer are in milliseconds
488 505 self.timer.Start(poll_interval)
489 506 self.Bind(wx.EVT_TIMER, self.on_timer)
490 507 self.func = func
491 508
492 509 def on_timer(self, event):
493 510 self.func()
494 511
495 512 # We need a custom wx.App to create our Frame subclass that has the
496 513 # wx.Timer to drive the ZMQ event loop.
497 514 class IPWxApp(wx.App):
498 515 def OnInit(self):
499 516 self.frame = TimerFrame(doi)
500 517 self.frame.Show(False)
501 518 return True
502 519
503 520 # The redirect=False here makes sure that wx doesn't replace
504 521 # sys.stdout/stderr with its own classes.
505 522 self.app = IPWxApp(redirect=False)
506 523 start_event_loop_wx(self.app)
507 524
508 525
509 526 class TkKernel(Kernel):
510 527 """A Kernel subclass with Tk support."""
511 528
512 529 def start(self):
513 530 """Start a Tk enabled event loop."""
514 531
515 532 import Tkinter
516 533 doi = self.do_one_iteration
517 534 # Tk uses milliseconds
518 535 poll_interval = int(1000*self._poll_interval)
519 536 # For Tkinter, we create a Tk object and call its withdraw method.
520 537 class Timer(object):
521 538 def __init__(self, func):
522 539 self.app = Tkinter.Tk()
523 540 self.app.withdraw()
524 541 self.func = func
525 542
526 543 def on_timer(self):
527 544 self.func()
528 545 self.app.after(poll_interval, self.on_timer)
529 546
530 547 def start(self):
531 548 self.on_timer() # Call it once to get things going.
532 549 self.app.mainloop()
533 550
534 551 self.timer = Timer(doi)
535 552 self.timer.start()
536 553
537 554
538 555 class GTKKernel(Kernel):
539 556 """A Kernel subclass with GTK support."""
540 557
541 558 def start(self):
542 559 """Start the kernel, coordinating with the GTK event loop"""
543 560 from .gui.gtkembed import GTKEmbed
544 561
545 562 gtk_kernel = GTKEmbed(self)
546 563 gtk_kernel.start()
547 564
548 565
549 566 #-----------------------------------------------------------------------------
550 567 # Kernel main and launch functions
551 568 #-----------------------------------------------------------------------------
552 569
553 570 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
554 571 executable=None, independent=False, pylab=False, colors=None):
555 572 """Launches a localhost kernel, binding to the specified ports.
556 573
557 574 Parameters
558 575 ----------
559 576 ip : str, optional
560 577 The ip address the kernel will bind to.
561 578
562 579 xrep_port : int, optional
563 580 The port to use for XREP channel.
564 581
565 582 pub_port : int, optional
566 583 The port to use for the SUB channel.
567 584
568 585 req_port : int, optional
569 586 The port to use for the REQ (raw input) channel.
570 587
571 588 hb_port : int, optional
572 589 The port to use for the hearbeat REP channel.
573 590
574 591 executable : str, optional (default sys.executable)
575 592 The Python executable to use for the kernel process.
576 593
577 594 independent : bool, optional (default False)
578 595 If set, the kernel process is guaranteed to survive if this process
579 596 dies. If not set, an effort is made to ensure that the kernel is killed
580 597 when this process dies. Note that in this case it is still good practice
581 598 to kill kernels manually before exiting.
582 599
583 600 pylab : bool or string, optional (default False)
584 601 If not False, the kernel will be launched with pylab enabled. If a
585 602 string is passed, matplotlib will use the specified backend. Otherwise,
586 603 matplotlib's default backend will be used.
587 604
588 605 colors : None or string, optional (default None)
589 606 If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
590 607
591 608 Returns
592 609 -------
593 610 A tuple of form:
594 611 (kernel_process, xrep_port, pub_port, req_port)
595 612 where kernel_process is a Popen object and the ports are integers.
596 613 """
597 614 extra_arguments = []
598 615 if pylab:
599 616 extra_arguments.append('--pylab')
600 617 if isinstance(pylab, basestring):
601 618 extra_arguments.append(pylab)
602 619 if ip is not None:
603 620 extra_arguments.append('--ip')
604 621 if isinstance(ip, basestring):
605 622 extra_arguments.append(ip)
606 623 if colors is not None:
607 624 extra_arguments.append('--colors')
608 625 extra_arguments.append(colors)
609 626 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
610 627 xrep_port, pub_port, req_port, hb_port,
611 628 executable, independent, extra_arguments)
612 629
613 630
614 631 def main():
615 632 """ The IPython kernel main entry point.
616 633 """
617 634 parser = make_argument_parser()
618 635 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
619 636 const='auto', help = \
620 637 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
621 638 given, the GUI backend is matplotlib's, otherwise use one of: \
622 639 ['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
623 640 parser.add_argument('--colors',
624 641 type=str, dest='colors',
625 642 help="Set the color scheme (NoColor, Linux, and LightBG).",
626 643 metavar='ZMQInteractiveShell.colors')
627 644 namespace = parser.parse_args()
628 645
629 646 kernel_class = Kernel
630 647
631 648 kernel_classes = {
632 649 'qt' : QtKernel,
633 650 'qt4': QtKernel,
634 651 'inline': Kernel,
635 652 'osx': TkKernel,
636 653 'wx' : WxKernel,
637 654 'tk' : TkKernel,
638 655 'gtk': GTKKernel,
639 656 }
640 657 if namespace.pylab:
641 658 if namespace.pylab == 'auto':
642 659 gui, backend = pylabtools.find_gui_and_backend()
643 660 else:
644 661 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
645 662 kernel_class = kernel_classes.get(gui)
646 663 if kernel_class is None:
647 664 raise ValueError('GUI is not supported: %r' % gui)
648 665 pylabtools.activate_matplotlib(backend)
649 666 if namespace.colors:
650 667 ZMQInteractiveShell.colors=namespace.colors
651 668
652 669 kernel = make_kernel(namespace, kernel_class, OutStream)
653 670
654 671 if namespace.pylab:
655 672 pylabtools.import_pylab(kernel.shell.user_ns, backend,
656 673 shell=kernel.shell)
657 674
658 675 start_kernel(namespace, kernel)
659 676
660 677
661 678 if __name__ == '__main__':
662 679 main()
@@ -1,920 +1,937
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 TODO
4 4 * Create logger to handle debugging and console messages.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2010 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Standard library imports.
19 19 import atexit
20 20 from Queue import Queue, Empty
21 21 from subprocess import Popen
22 22 import signal
23 23 import sys
24 24 from threading import Thread
25 25 import time
26 26 import logging
27 27
28 28 # System library imports.
29 29 import zmq
30 30 from zmq import POLLIN, POLLOUT, POLLERR
31 31 from zmq.eventloop import ioloop
32 32
33 33 # Local imports.
34 34 from IPython.utils import io
35 35 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
36 36 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
37 37 from session import Session, Message
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Constants and exceptions
41 41 #-----------------------------------------------------------------------------
42 42
43 43 class InvalidPortNumber(Exception):
44 44 pass
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Utility functions
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # some utilities to validate message structure, these might get moved elsewhere
51 51 # if they prove to have more generic utility
52 52
53 53 def validate_string_list(lst):
54 54 """Validate that the input is a list of strings.
55 55
56 56 Raises ValueError if not."""
57 57 if not isinstance(lst, list):
58 58 raise ValueError('input %r must be a list' % lst)
59 59 for x in lst:
60 60 if not isinstance(x, basestring):
61 61 raise ValueError('element %r in list must be a string' % x)
62 62
63 63
64 64 def validate_string_dict(dct):
65 65 """Validate that the input is a dict with string keys and values.
66 66
67 67 Raises ValueError if not."""
68 68 for k,v in dct.iteritems():
69 69 if not isinstance(k, basestring):
70 70 raise ValueError('key %r in dict must be a string' % k)
71 71 if not isinstance(v, basestring):
72 72 raise ValueError('value %r in dict must be a string' % v)
73 73
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # ZMQ Socket Channel classes
77 77 #-----------------------------------------------------------------------------
78 78
79 79 class ZmqSocketChannel(Thread):
80 80 """The base class for the channels that use ZMQ sockets.
81 81 """
82 82 context = None
83 83 session = None
84 84 socket = None
85 85 ioloop = None
86 86 iostate = None
87 87 _address = None
88 88
89 89 def __init__(self, context, session, address):
90 90 """Create a channel
91 91
92 92 Parameters
93 93 ----------
94 94 context : :class:`zmq.Context`
95 95 The ZMQ context to use.
96 96 session : :class:`session.Session`
97 97 The session to use.
98 98 address : tuple
99 99 Standard (ip, port) tuple that the kernel is listening on.
100 100 """
101 101 super(ZmqSocketChannel, self).__init__()
102 102 self.daemon = True
103 103
104 104 self.context = context
105 105 self.session = session
106 106 if address[1] == 0:
107 107 message = 'The port number for a channel cannot be 0.'
108 108 raise InvalidPortNumber(message)
109 109 self._address = address
110 110
111 111 def stop(self):
112 112 """Stop the channel's activity.
113 113
114 114 This calls :method:`Thread.join` and returns when the thread
115 115 terminates. :class:`RuntimeError` will be raised if
116 116 :method:`self.start` is called again.
117 117 """
118 118 self.join()
119 119
120 120 @property
121 121 def address(self):
122 122 """Get the channel's address as an (ip, port) tuple.
123 123
124 124 By the default, the address is (localhost, 0), where 0 means a random
125 125 port.
126 126 """
127 127 return self._address
128 128
129 129 def add_io_state(self, state):
130 130 """Add IO state to the eventloop.
131 131
132 132 Parameters
133 133 ----------
134 134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
135 135 The IO state flag to set.
136 136
137 137 This is thread safe as it uses the thread safe IOLoop.add_callback.
138 138 """
139 139 def add_io_state_callback():
140 140 if not self.iostate & state:
141 141 self.iostate = self.iostate | state
142 142 self.ioloop.update_handler(self.socket, self.iostate)
143 143 self.ioloop.add_callback(add_io_state_callback)
144 144
145 145 def drop_io_state(self, state):
146 146 """Drop IO state from the eventloop.
147 147
148 148 Parameters
149 149 ----------
150 150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
151 151 The IO state flag to set.
152 152
153 153 This is thread safe as it uses the thread safe IOLoop.add_callback.
154 154 """
155 155 def drop_io_state_callback():
156 156 if self.iostate & state:
157 157 self.iostate = self.iostate & (~state)
158 158 self.ioloop.update_handler(self.socket, self.iostate)
159 159 self.ioloop.add_callback(drop_io_state_callback)
160 160
161 161
162 162 class XReqSocketChannel(ZmqSocketChannel):
163 163 """The XREQ channel for issues request/replies to the kernel.
164 164 """
165 165
166 166 command_queue = None
167 167
168 168 def __init__(self, context, session, address):
169 169 super(XReqSocketChannel, self).__init__(context, session, address)
170 170 self.command_queue = Queue()
171 171 self.ioloop = ioloop.IOLoop()
172 172
173 173 def run(self):
174 174 """The thread's main activity. Call start() instead."""
175 175 self.socket = self.context.socket(zmq.XREQ)
176 176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
177 177 self.socket.connect('tcp://%s:%i' % self.address)
178 178 self.iostate = POLLERR|POLLIN
179 179 self.ioloop.add_handler(self.socket, self._handle_events,
180 180 self.iostate)
181 181 self.ioloop.start()
182 182
183 183 def stop(self):
184 184 self.ioloop.stop()
185 185 super(XReqSocketChannel, self).stop()
186 186
187 187 def call_handlers(self, msg):
188 188 """This method is called in the ioloop thread when a message arrives.
189 189
190 190 Subclasses should override this method to handle incoming messages.
191 191 It is important to remember that this method is called in the thread
192 192 so that some logic must be done to ensure that the application leve
193 193 handlers are called in the application thread.
194 194 """
195 195 raise NotImplementedError('call_handlers must be defined in a subclass.')
196 196
197 197 def execute(self, code, silent=False,
198 198 user_variables=None, user_expressions=None):
199 199 """Execute code in the kernel.
200 200
201 201 Parameters
202 202 ----------
203 203 code : str
204 204 A string of Python code.
205 205
206 206 silent : bool, optional (default False)
207 207 If set, the kernel will execute the code as quietly possible.
208 208
209 209 user_variables : list, optional
210 210 A list of variable names to pull from the user's namespace. They
211 211 will come back as a dict with these names as keys and their
212 212 :func:`repr` as values.
213 213
214 214 user_expressions : dict, optional
215 215 A dict with string keys and to pull from the user's
216 216 namespace. They will come back as a dict with these names as keys
217 217 and their :func:`repr` as values.
218 218
219 219 Returns
220 220 -------
221 221 The msg_id of the message sent.
222 222 """
223 223 if user_variables is None:
224 224 user_variables = []
225 225 if user_expressions is None:
226 226 user_expressions = {}
227 227
228 228 # Don't waste network traffic if inputs are invalid
229 229 if not isinstance(code, basestring):
230 230 raise ValueError('code %r must be a string' % code)
231 231 validate_string_list(user_variables)
232 232 validate_string_dict(user_expressions)
233 233
234 234 # Create class for content/msg creation. Related to, but possibly
235 235 # not in Session.
236 236 content = dict(code=code, silent=silent,
237 237 user_variables=user_variables,
238 238 user_expressions=user_expressions)
239 239 msg = self.session.msg('execute_request', content)
240 240 self._queue_request(msg)
241 241 return msg['header']['msg_id']
242 242
243 243 def complete(self, text, line, cursor_pos, block=None):
244 244 """Tab complete text in the kernel's namespace.
245 245
246 246 Parameters
247 247 ----------
248 248 text : str
249 249 The text to complete.
250 250 line : str
251 251 The full line of text that is the surrounding context for the
252 252 text to complete.
253 253 cursor_pos : int
254 254 The position of the cursor in the line where the completion was
255 255 requested.
256 256 block : str, optional
257 257 The full block of code in which the completion is being requested.
258 258
259 259 Returns
260 260 -------
261 261 The msg_id of the message sent.
262 262 """
263 263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
264 264 msg = self.session.msg('complete_request', content)
265 265 self._queue_request(msg)
266 266 return msg['header']['msg_id']
267 267
268 268 def object_info(self, oname):
269 269 """Get metadata information about an object.
270 270
271 271 Parameters
272 272 ----------
273 273 oname : str
274 274 A string specifying the object name.
275 275
276 276 Returns
277 277 -------
278 278 The msg_id of the message sent.
279 279 """
280 280 content = dict(oname=oname)
281 281 msg = self.session.msg('object_info_request', content)
282 282 self._queue_request(msg)
283 283 return msg['header']['msg_id']
284 284
285 def history_tail(self, n=10, raw=True, output=False):
286 """Get the history list.
285 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
286 """Get entries from the history list.
287 287
288 288 Parameters
289 289 ----------
290 n : int
291 The number of lines of history to get.
292 290 raw : bool
293 291 If True, return the raw input.
294 292 output : bool
295 293 If True, then return the output as well.
294 hist_access_type : str
295 'range' (fill in session, start and stop params), 'tail' (fill in n)
296 or 'search' (fill in pattern param).
297
298 session : int
299 For a range request, the session from which to get lines. Session
300 numbers are positive integers; negative ones count back from the
301 current session.
302 start : int
303 The first line number of a history range.
304 stop : int
305 The final (excluded) line number of a history range.
306
307 n : int
308 The number of lines of history to get for a tail request.
309
310 pattern : str
311 The glob-syntax pattern for a search request.
296 312
297 313 Returns
298 314 -------
299 315 The msg_id of the message sent.
300 316 """
301 content = dict(n=n, raw=raw, output=output)
302 msg = self.session.msg('history_tail_request', content)
317 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
318 **kwargs)
319 msg = self.session.msg('history_request', content)
303 320 self._queue_request(msg)
304 321 return msg['header']['msg_id']
305 322
306 323 def shutdown(self, restart=False):
307 324 """Request an immediate kernel shutdown.
308 325
309 326 Upon receipt of the (empty) reply, client code can safely assume that
310 327 the kernel has shut down and it's safe to forcefully terminate it if
311 328 it's still alive.
312 329
313 330 The kernel will send the reply via a function registered with Python's
314 331 atexit module, ensuring it's truly done as the kernel is done with all
315 332 normal operation.
316 333 """
317 334 # Send quit message to kernel. Once we implement kernel-side setattr,
318 335 # this should probably be done that way, but for now this will do.
319 336 msg = self.session.msg('shutdown_request', {'restart':restart})
320 337 self._queue_request(msg)
321 338 return msg['header']['msg_id']
322 339
323 340 def _handle_events(self, socket, events):
324 341 if events & POLLERR:
325 342 self._handle_err()
326 343 if events & POLLOUT:
327 344 self._handle_send()
328 345 if events & POLLIN:
329 346 self._handle_recv()
330 347
331 348 def _handle_recv(self):
332 349 ident,msg = self.session.recv(self.socket, 0)
333 350 self.call_handlers(msg)
334 351
335 352 def _handle_send(self):
336 353 try:
337 354 msg = self.command_queue.get(False)
338 355 except Empty:
339 356 pass
340 357 else:
341 358 self.session.send(self.socket,msg)
342 359 if self.command_queue.empty():
343 360 self.drop_io_state(POLLOUT)
344 361
345 362 def _handle_err(self):
346 363 # We don't want to let this go silently, so eventually we should log.
347 364 raise zmq.ZMQError()
348 365
349 366 def _queue_request(self, msg):
350 367 self.command_queue.put(msg)
351 368 self.add_io_state(POLLOUT)
352 369
353 370
354 371 class SubSocketChannel(ZmqSocketChannel):
355 372 """The SUB channel which listens for messages that the kernel publishes.
356 373 """
357 374
358 375 def __init__(self, context, session, address):
359 376 super(SubSocketChannel, self).__init__(context, session, address)
360 377 self.ioloop = ioloop.IOLoop()
361 378
362 379 def run(self):
363 380 """The thread's main activity. Call start() instead."""
364 381 self.socket = self.context.socket(zmq.SUB)
365 382 self.socket.setsockopt(zmq.SUBSCRIBE,'')
366 383 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
367 384 self.socket.connect('tcp://%s:%i' % self.address)
368 385 self.iostate = POLLIN|POLLERR
369 386 self.ioloop.add_handler(self.socket, self._handle_events,
370 387 self.iostate)
371 388 self.ioloop.start()
372 389
373 390 def stop(self):
374 391 self.ioloop.stop()
375 392 super(SubSocketChannel, self).stop()
376 393
377 394 def call_handlers(self, msg):
378 395 """This method is called in the ioloop thread when a message arrives.
379 396
380 397 Subclasses should override this method to handle incoming messages.
381 398 It is important to remember that this method is called in the thread
382 399 so that some logic must be done to ensure that the application leve
383 400 handlers are called in the application thread.
384 401 """
385 402 raise NotImplementedError('call_handlers must be defined in a subclass.')
386 403
387 404 def flush(self, timeout=1.0):
388 405 """Immediately processes all pending messages on the SUB channel.
389 406
390 407 Callers should use this method to ensure that :method:`call_handlers`
391 408 has been called for all messages that have been received on the
392 409 0MQ SUB socket of this channel.
393 410
394 411 This method is thread safe.
395 412
396 413 Parameters
397 414 ----------
398 415 timeout : float, optional
399 416 The maximum amount of time to spend flushing, in seconds. The
400 417 default is one second.
401 418 """
402 419 # We do the IOLoop callback process twice to ensure that the IOLoop
403 420 # gets to perform at least one full poll.
404 421 stop_time = time.time() + timeout
405 422 for i in xrange(2):
406 423 self._flushed = False
407 424 self.ioloop.add_callback(self._flush)
408 425 while not self._flushed and time.time() < stop_time:
409 426 time.sleep(0.01)
410 427
411 428 def _handle_events(self, socket, events):
412 429 # Turn on and off POLLOUT depending on if we have made a request
413 430 if events & POLLERR:
414 431 self._handle_err()
415 432 if events & POLLIN:
416 433 self._handle_recv()
417 434
418 435 def _handle_err(self):
419 436 # We don't want to let this go silently, so eventually we should log.
420 437 raise zmq.ZMQError()
421 438
422 439 def _handle_recv(self):
423 440 # Get all of the messages we can
424 441 while True:
425 442 try:
426 443 ident,msg = self.session.recv(self.socket)
427 444 except zmq.ZMQError:
428 445 # Check the errno?
429 446 # Will this trigger POLLERR?
430 447 break
431 448 else:
432 449 if msg is None:
433 450 break
434 451 self.call_handlers(msg)
435 452
436 453 def _flush(self):
437 454 """Callback for :method:`self.flush`."""
438 455 self._flushed = True
439 456
440 457
441 458 class RepSocketChannel(ZmqSocketChannel):
442 459 """A reply channel to handle raw_input requests that the kernel makes."""
443 460
444 461 msg_queue = None
445 462
446 463 def __init__(self, context, session, address):
447 464 super(RepSocketChannel, self).__init__(context, session, address)
448 465 self.ioloop = ioloop.IOLoop()
449 466 self.msg_queue = Queue()
450 467
451 468 def run(self):
452 469 """The thread's main activity. Call start() instead."""
453 470 self.socket = self.context.socket(zmq.XREQ)
454 471 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
455 472 self.socket.connect('tcp://%s:%i' % self.address)
456 473 self.iostate = POLLERR|POLLIN
457 474 self.ioloop.add_handler(self.socket, self._handle_events,
458 475 self.iostate)
459 476 self.ioloop.start()
460 477
461 478 def stop(self):
462 479 self.ioloop.stop()
463 480 super(RepSocketChannel, self).stop()
464 481
465 482 def call_handlers(self, msg):
466 483 """This method is called in the ioloop thread when a message arrives.
467 484
468 485 Subclasses should override this method to handle incoming messages.
469 486 It is important to remember that this method is called in the thread
470 487 so that some logic must be done to ensure that the application leve
471 488 handlers are called in the application thread.
472 489 """
473 490 raise NotImplementedError('call_handlers must be defined in a subclass.')
474 491
475 492 def input(self, string):
476 493 """Send a string of raw input to the kernel."""
477 494 content = dict(value=string)
478 495 msg = self.session.msg('input_reply', content)
479 496 self._queue_reply(msg)
480 497
481 498 def _handle_events(self, socket, events):
482 499 if events & POLLERR:
483 500 self._handle_err()
484 501 if events & POLLOUT:
485 502 self._handle_send()
486 503 if events & POLLIN:
487 504 self._handle_recv()
488 505
489 506 def _handle_recv(self):
490 507 ident,msg = self.session.recv(self.socket, 0)
491 508 self.call_handlers(msg)
492 509
493 510 def _handle_send(self):
494 511 try:
495 512 msg = self.msg_queue.get(False)
496 513 except Empty:
497 514 pass
498 515 else:
499 516 self.session.send(self.socket,msg)
500 517 if self.msg_queue.empty():
501 518 self.drop_io_state(POLLOUT)
502 519
503 520 def _handle_err(self):
504 521 # We don't want to let this go silently, so eventually we should log.
505 522 raise zmq.ZMQError()
506 523
507 524 def _queue_reply(self, msg):
508 525 self.msg_queue.put(msg)
509 526 self.add_io_state(POLLOUT)
510 527
511 528
512 529 class HBSocketChannel(ZmqSocketChannel):
513 530 """The heartbeat channel which monitors the kernel heartbeat.
514 531
515 532 Note that the heartbeat channel is paused by default. As long as you start
516 533 this channel, the kernel manager will ensure that it is paused and un-paused
517 534 as appropriate.
518 535 """
519 536
520 537 time_to_dead = 3.0
521 538 socket = None
522 539 poller = None
523 540 _running = None
524 541 _pause = None
525 542
526 543 def __init__(self, context, session, address):
527 544 super(HBSocketChannel, self).__init__(context, session, address)
528 545 self._running = False
529 546 self._pause = True
530 547
531 548 def _create_socket(self):
532 549 self.socket = self.context.socket(zmq.REQ)
533 550 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
534 551 self.socket.connect('tcp://%s:%i' % self.address)
535 552 self.poller = zmq.Poller()
536 553 self.poller.register(self.socket, zmq.POLLIN)
537 554
538 555 def run(self):
539 556 """The thread's main activity. Call start() instead."""
540 557 self._create_socket()
541 558 self._running = True
542 559 while self._running:
543 560 if self._pause:
544 561 time.sleep(self.time_to_dead)
545 562 else:
546 563 since_last_heartbeat = 0.0
547 564 request_time = time.time()
548 565 try:
549 566 #io.rprint('Ping from HB channel') # dbg
550 567 self.socket.send(b'ping')
551 568 except zmq.ZMQError, e:
552 569 #io.rprint('*** HB Error:', e) # dbg
553 570 if e.errno == zmq.EFSM:
554 571 #io.rprint('sleep...', self.time_to_dead) # dbg
555 572 time.sleep(self.time_to_dead)
556 573 self._create_socket()
557 574 else:
558 575 raise
559 576 else:
560 577 while True:
561 578 try:
562 579 self.socket.recv(zmq.NOBLOCK)
563 580 except zmq.ZMQError, e:
564 581 #io.rprint('*** HB Error 2:', e) # dbg
565 582 if e.errno == zmq.EAGAIN:
566 583 before_poll = time.time()
567 584 until_dead = self.time_to_dead - (before_poll -
568 585 request_time)
569 586
570 587 # When the return value of poll() is an empty
571 588 # list, that is when things have gone wrong
572 589 # (zeromq bug). As long as it is not an empty
573 590 # list, poll is working correctly even if it
574 591 # returns quickly. Note: poll timeout is in
575 592 # milliseconds.
576 593 self.poller.poll(1000*until_dead)
577 594
578 595 since_last_heartbeat = time.time()-request_time
579 596 if since_last_heartbeat > self.time_to_dead:
580 597 self.call_handlers(since_last_heartbeat)
581 598 break
582 599 else:
583 600 # FIXME: We should probably log this instead.
584 601 raise
585 602 else:
586 603 until_dead = self.time_to_dead - (time.time() -
587 604 request_time)
588 605 if until_dead > 0.0:
589 606 #io.rprint('sleep...', self.time_to_dead) # dbg
590 607 time.sleep(until_dead)
591 608 break
592 609
593 610 def pause(self):
594 611 """Pause the heartbeat."""
595 612 self._pause = True
596 613
597 614 def unpause(self):
598 615 """Unpause the heartbeat."""
599 616 self._pause = False
600 617
601 618 def is_beating(self):
602 619 """Is the heartbeat running and not paused."""
603 620 if self.is_alive() and not self._pause:
604 621 return True
605 622 else:
606 623 return False
607 624
608 625 def stop(self):
609 626 self._running = False
610 627 super(HBSocketChannel, self).stop()
611 628
612 629 def call_handlers(self, since_last_heartbeat):
613 630 """This method is called in the ioloop thread when a message arrives.
614 631
615 632 Subclasses should override this method to handle incoming messages.
616 633 It is important to remember that this method is called in the thread
617 634 so that some logic must be done to ensure that the application leve
618 635 handlers are called in the application thread.
619 636 """
620 637 raise NotImplementedError('call_handlers must be defined in a subclass.')
621 638
622 639
623 640 #-----------------------------------------------------------------------------
624 641 # Main kernel manager class
625 642 #-----------------------------------------------------------------------------
626 643
627 644 class KernelManager(HasTraits):
628 645 """ Manages a kernel for a frontend.
629 646
630 647 The SUB channel is for the frontend to receive messages published by the
631 648 kernel.
632 649
633 650 The REQ channel is for the frontend to make requests of the kernel.
634 651
635 652 The REP channel is for the kernel to request stdin (raw_input) from the
636 653 frontend.
637 654 """
638 655 # The PyZMQ Context to use for communication with the kernel.
639 656 context = Instance(zmq.Context,(),{})
640 657
641 658 # The Session to use for communication with the kernel.
642 659 session = Instance(Session,(),{})
643 660
644 661 # The kernel process with which the KernelManager is communicating.
645 662 kernel = Instance(Popen)
646 663
647 664 # The addresses for the communication channels.
648 665 xreq_address = TCPAddress((LOCALHOST, 0))
649 666 sub_address = TCPAddress((LOCALHOST, 0))
650 667 rep_address = TCPAddress((LOCALHOST, 0))
651 668 hb_address = TCPAddress((LOCALHOST, 0))
652 669
653 670 # The classes to use for the various channels.
654 671 xreq_channel_class = Type(XReqSocketChannel)
655 672 sub_channel_class = Type(SubSocketChannel)
656 673 rep_channel_class = Type(RepSocketChannel)
657 674 hb_channel_class = Type(HBSocketChannel)
658 675
659 676 # Protected traits.
660 677 _launch_args = Any
661 678 _xreq_channel = Any
662 679 _sub_channel = Any
663 680 _rep_channel = Any
664 681 _hb_channel = Any
665 682
666 683 def __init__(self, **kwargs):
667 684 super(KernelManager, self).__init__(**kwargs)
668 685 # Uncomment this to try closing the context.
669 686 # atexit.register(self.context.close)
670 687
671 688 #--------------------------------------------------------------------------
672 689 # Channel management methods:
673 690 #--------------------------------------------------------------------------
674 691
675 692 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
676 693 """Starts the channels for this kernel.
677 694
678 695 This will create the channels if they do not exist and then start
679 696 them. If port numbers of 0 are being used (random ports) then you
680 697 must first call :method:`start_kernel`. If the channels have been
681 698 stopped and you call this, :class:`RuntimeError` will be raised.
682 699 """
683 700 if xreq:
684 701 self.xreq_channel.start()
685 702 if sub:
686 703 self.sub_channel.start()
687 704 if rep:
688 705 self.rep_channel.start()
689 706 if hb:
690 707 self.hb_channel.start()
691 708
692 709 def stop_channels(self):
693 710 """Stops all the running channels for this kernel.
694 711 """
695 712 if self.xreq_channel.is_alive():
696 713 self.xreq_channel.stop()
697 714 if self.sub_channel.is_alive():
698 715 self.sub_channel.stop()
699 716 if self.rep_channel.is_alive():
700 717 self.rep_channel.stop()
701 718 if self.hb_channel.is_alive():
702 719 self.hb_channel.stop()
703 720
704 721 @property
705 722 def channels_running(self):
706 723 """Are any of the channels created and running?"""
707 724 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
708 725 self.rep_channel.is_alive() or self.hb_channel.is_alive())
709 726
710 727 #--------------------------------------------------------------------------
711 728 # Kernel process management methods:
712 729 #--------------------------------------------------------------------------
713 730
714 731 def start_kernel(self, **kw):
715 732 """Starts a kernel process and configures the manager to use it.
716 733
717 734 If random ports (port=0) are being used, this method must be called
718 735 before the channels are created.
719 736
720 737 Parameters:
721 738 -----------
722 739 ipython : bool, optional (default True)
723 740 Whether to use an IPython kernel instead of a plain Python kernel.
724 741
725 742 **kw : optional
726 743 See respective options for IPython and Python kernels.
727 744 """
728 745 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
729 746 self.rep_address, self.hb_address
730 747 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
731 748 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
732 749 raise RuntimeError("Can only launch a kernel on a local interface. "
733 750 "Make sure that the '*_address' attributes are "
734 751 "configured properly. "
735 752 "Currently valid addresses are: %s"%LOCAL_IPS
736 753 )
737 754
738 755 self._launch_args = kw.copy()
739 756 if kw.pop('ipython', True):
740 757 from ipkernel import launch_kernel
741 758 else:
742 759 from pykernel import launch_kernel
743 760 self.kernel, xrep, pub, req, _hb = launch_kernel(
744 761 xrep_port=xreq[1], pub_port=sub[1],
745 762 req_port=rep[1], hb_port=hb[1], **kw)
746 763 self.xreq_address = (xreq[0], xrep)
747 764 self.sub_address = (sub[0], pub)
748 765 self.rep_address = (rep[0], req)
749 766 self.hb_address = (hb[0], _hb)
750 767
751 768 def shutdown_kernel(self, restart=False):
752 769 """ Attempts to the stop the kernel process cleanly. If the kernel
753 770 cannot be stopped, it is killed, if possible.
754 771 """
755 772 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
756 773 if sys.platform == 'win32':
757 774 self.kill_kernel()
758 775 return
759 776
760 777 # Pause the heart beat channel if it exists.
761 778 if self._hb_channel is not None:
762 779 self._hb_channel.pause()
763 780
764 781 # Don't send any additional kernel kill messages immediately, to give
765 782 # the kernel a chance to properly execute shutdown actions. Wait for at
766 783 # most 1s, checking every 0.1s.
767 784 self.xreq_channel.shutdown(restart=restart)
768 785 for i in range(10):
769 786 if self.is_alive:
770 787 time.sleep(0.1)
771 788 else:
772 789 break
773 790 else:
774 791 # OK, we've waited long enough.
775 792 if self.has_kernel:
776 793 self.kill_kernel()
777 794
778 795 def restart_kernel(self, now=False, **kw):
779 796 """Restarts a kernel with the arguments that were used to launch it.
780 797
781 798 If the old kernel was launched with random ports, the same ports will be
782 799 used for the new kernel.
783 800
784 801 Parameters
785 802 ----------
786 803 now : bool, optional
787 804 If True, the kernel is forcefully restarted *immediately*, without
788 805 having a chance to do any cleanup action. Otherwise the kernel is
789 806 given 1s to clean up before a forceful restart is issued.
790 807
791 808 In all cases the kernel is restarted, the only difference is whether
792 809 it is given a chance to perform a clean shutdown or not.
793 810
794 811 **kw : optional
795 812 Any options specified here will replace those used to launch the
796 813 kernel.
797 814 """
798 815 if self._launch_args is None:
799 816 raise RuntimeError("Cannot restart the kernel. "
800 817 "No previous call to 'start_kernel'.")
801 818 else:
802 819 # Stop currently running kernel.
803 820 if self.has_kernel:
804 821 if now:
805 822 self.kill_kernel()
806 823 else:
807 824 self.shutdown_kernel(restart=True)
808 825
809 826 # Start new kernel.
810 827 self._launch_args.update(kw)
811 828 self.start_kernel(**self._launch_args)
812 829
813 830 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
814 831 # unless there is some delay here.
815 832 if sys.platform == 'win32':
816 833 time.sleep(0.2)
817 834
818 835 @property
819 836 def has_kernel(self):
820 837 """Returns whether a kernel process has been specified for the kernel
821 838 manager.
822 839 """
823 840 return self.kernel is not None
824 841
825 842 def kill_kernel(self):
826 843 """ Kill the running kernel. """
827 844 if self.has_kernel:
828 845 # Pause the heart beat channel if it exists.
829 846 if self._hb_channel is not None:
830 847 self._hb_channel.pause()
831 848
832 849 # Attempt to kill the kernel.
833 850 try:
834 851 self.kernel.kill()
835 852 except OSError, e:
836 853 # In Windows, we will get an Access Denied error if the process
837 854 # has already terminated. Ignore it.
838 855 if not (sys.platform == 'win32' and e.winerror == 5):
839 856 raise
840 857 self.kernel = None
841 858 else:
842 859 raise RuntimeError("Cannot kill kernel. No kernel is running!")
843 860
844 861 def interrupt_kernel(self):
845 862 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
846 863 well supported on all platforms.
847 864 """
848 865 if self.has_kernel:
849 866 if sys.platform == 'win32':
850 867 from parentpoller import ParentPollerWindows as Poller
851 868 Poller.send_interrupt(self.kernel.win32_interrupt_event)
852 869 else:
853 870 self.kernel.send_signal(signal.SIGINT)
854 871 else:
855 872 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
856 873
857 874 def signal_kernel(self, signum):
858 875 """ Sends a signal to the kernel. Note that since only SIGTERM is
859 876 supported on Windows, this function is only useful on Unix systems.
860 877 """
861 878 if self.has_kernel:
862 879 self.kernel.send_signal(signum)
863 880 else:
864 881 raise RuntimeError("Cannot signal kernel. No kernel is running!")
865 882
866 883 @property
867 884 def is_alive(self):
868 885 """Is the kernel process still running?"""
869 886 # FIXME: not using a heartbeat means this method is broken for any
870 887 # remote kernel, it's only capable of handling local kernels.
871 888 if self.has_kernel:
872 889 if self.kernel.poll() is None:
873 890 return True
874 891 else:
875 892 return False
876 893 else:
877 894 # We didn't start the kernel with this KernelManager so we don't
878 895 # know if it is running. We should use a heartbeat for this case.
879 896 return True
880 897
881 898 #--------------------------------------------------------------------------
882 899 # Channels used for communication with the kernel:
883 900 #--------------------------------------------------------------------------
884 901
885 902 @property
886 903 def xreq_channel(self):
887 904 """Get the REQ socket channel object to make requests of the kernel."""
888 905 if self._xreq_channel is None:
889 906 self._xreq_channel = self.xreq_channel_class(self.context,
890 907 self.session,
891 908 self.xreq_address)
892 909 return self._xreq_channel
893 910
894 911 @property
895 912 def sub_channel(self):
896 913 """Get the SUB socket channel object."""
897 914 if self._sub_channel is None:
898 915 self._sub_channel = self.sub_channel_class(self.context,
899 916 self.session,
900 917 self.sub_address)
901 918 return self._sub_channel
902 919
903 920 @property
904 921 def rep_channel(self):
905 922 """Get the REP socket channel object to handle stdin (raw_input)."""
906 923 if self._rep_channel is None:
907 924 self._rep_channel = self.rep_channel_class(self.context,
908 925 self.session,
909 926 self.rep_address)
910 927 return self._rep_channel
911 928
912 929 @property
913 930 def hb_channel(self):
914 931 """Get the heartbeat socket channel object to check that the
915 932 kernel is alive."""
916 933 if self._hb_channel is None:
917 934 self._hb_channel = self.hb_channel_class(self.context,
918 935 self.session,
919 936 self.hb_address)
920 937 return self._hb_channel
@@ -1,924 +1,937
1 1 .. _messaging:
2 2
3 3 ======================
4 4 Messaging in IPython
5 5 ======================
6 6
7 7
8 8 Introduction
9 9 ============
10 10
11 11 This document explains the basic communications design and messaging
12 12 specification for how the various IPython objects interact over a network
13 13 transport. The current implementation uses the ZeroMQ_ library for messaging
14 14 within and between hosts.
15 15
16 16 .. Note::
17 17
18 18 This document should be considered the authoritative description of the
19 19 IPython messaging protocol, and all developers are strongly encouraged to
20 20 keep it updated as the implementation evolves, so that we have a single
21 21 common reference for all protocol details.
22 22
23 23 The basic design is explained in the following diagram:
24 24
25 25 .. image:: frontend-kernel.png
26 26 :width: 450px
27 27 :alt: IPython kernel/frontend messaging architecture.
28 28 :align: center
29 29 :target: ../_images/frontend-kernel.png
30 30
31 31 A single kernel can be simultaneously connected to one or more frontends. The
32 32 kernel has three sockets that serve the following functions:
33 33
34 34 1. REQ: this socket is connected to a *single* frontend at a time, and it allows
35 35 the kernel to request input from a frontend when :func:`raw_input` is called.
36 36 The frontend holding the matching REP socket acts as a 'virtual keyboard'
37 37 for the kernel while this communication is happening (illustrated in the
38 38 figure by the black outline around the central keyboard). In practice,
39 39 frontends may display such kernel requests using a special input widget or
40 40 otherwise indicating that the user is to type input for the kernel instead
41 41 of normal commands in the frontend.
42 42
43 43 2. XREP: this single sockets allows multiple incoming connections from
44 44 frontends, and this is the socket where requests for code execution, object
45 45 information, prompts, etc. are made to the kernel by any frontend. The
46 46 communication on this socket is a sequence of request/reply actions from
47 47 each frontend and the kernel.
48 48
49 49 3. PUB: this socket is the 'broadcast channel' where the kernel publishes all
50 50 side effects (stdout, stderr, etc.) as well as the requests coming from any
51 51 client over the XREP socket and its own requests on the REP socket. There
52 52 are a number of actions in Python which generate side effects: :func:`print`
53 53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
54 54 a multi-client scenario, we want all frontends to be able to know what each
55 55 other has sent to the kernel (this can be useful in collaborative scenarios,
56 56 for example). This socket allows both side effects and the information
57 57 about communications taking place with one client over the XREQ/XREP channel
58 58 to be made available to all clients in a uniform manner.
59 59
60 60 All messages are tagged with enough information (details below) for clients
61 61 to know which messages come from their own interaction with the kernel and
62 62 which ones are from other clients, so they can display each type
63 63 appropriately.
64 64
65 65 The actual format of the messages allowed on each of these channels is
66 66 specified below. Messages are dicts of dicts with string keys and values that
67 67 are reasonably representable in JSON. Our current implementation uses JSON
68 68 explicitly as its message format, but this shouldn't be considered a permanent
69 69 feature. As we've discovered that JSON has non-trivial performance issues due
70 70 to excessive copying, we may in the future move to a pure pickle-based raw
71 71 message format. However, it should be possible to easily convert from the raw
72 72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
73 73 As long as it's easy to make a JSON version of the objects that is a faithful
74 74 representation of all the data, we can communicate with such clients.
75 75
76 76 .. Note::
77 77
78 78 Not all of these have yet been fully fleshed out, but the key ones are, see
79 79 kernel and frontend files for actual implementation details.
80 80
81 81
82 82 Python functional API
83 83 =====================
84 84
85 85 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
86 86 should develop, at a few key points, functional forms of all the requests that
87 87 take arguments in this manner and automatically construct the necessary dict
88 88 for sending.
89 89
90 90
91 91 General Message Format
92 92 ======================
93 93
94 94 All messages send or received by any IPython process should have the following
95 95 generic structure::
96 96
97 97 {
98 98 # The message header contains a pair of unique identifiers for the
99 99 # originating session and the actual message id, in addition to the
100 100 # username for the process that generated the message. This is useful in
101 101 # collaborative settings where multiple users may be interacting with the
102 102 # same kernel simultaneously, so that frontends can label the various
103 103 # messages in a meaningful way.
104 104 'header' : { 'msg_id' : uuid,
105 105 'username' : str,
106 106 'session' : uuid
107 107 },
108 108
109 109 # In a chain of messages, the header from the parent is copied so that
110 110 # clients can track where messages come from.
111 111 'parent_header' : dict,
112 112
113 113 # All recognized message type strings are listed below.
114 114 'msg_type' : str,
115 115
116 116 # The actual content of the message must be a dict, whose structure
117 117 # depends on the message type.x
118 118 'content' : dict,
119 119 }
120 120
121 121 For each message type, the actual content will differ and all existing message
122 122 types are specified in what follows of this document.
123 123
124 124
125 125 Messages on the XREP/XREQ socket
126 126 ================================
127 127
128 128 .. _execute:
129 129
130 130 Execute
131 131 -------
132 132
133 133 This message type is used by frontends to ask the kernel to execute code on
134 134 behalf of the user, in a namespace reserved to the user's variables (and thus
135 135 separate from the kernel's own internal code and variables).
136 136
137 137 Message type: ``execute_request``::
138 138
139 139 content = {
140 140 # Source code to be executed by the kernel, one or more lines.
141 141 'code' : str,
142 142
143 143 # A boolean flag which, if True, signals the kernel to execute this
144 144 # code as quietly as possible. This means that the kernel will compile
145 145 # the code witIPython/core/tests/h 'exec' instead of 'single' (so
146 146 # sys.displayhook will not fire), and will *not*:
147 147 # - broadcast exceptions on the PUB socket
148 148 # - do any logging
149 149 # - populate any history
150 150 #
151 151 # The default is False.
152 152 'silent' : bool,
153 153
154 154 # A list of variable names from the user's namespace to be retrieved. What
155 155 # returns is a JSON string of the variable's repr(), not a python object.
156 156 'user_variables' : list,
157 157
158 158 # Similarly, a dict mapping names to expressions to be evaluated in the
159 159 # user's dict.
160 160 'user_expressions' : dict,
161 161 }
162 162
163 163 The ``code`` field contains a single string (possibly multiline). The kernel
164 164 is responsible for splitting this into one or more independent execution blocks
165 165 and deciding whether to compile these in 'single' or 'exec' mode (see below for
166 166 detailed execution semantics).
167 167
168 168 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
169 169 the notion of a prompt string that allowed arbitrary code to be evaluated, and
170 170 this was put to good use by many in creating prompts that displayed system
171 171 status, path information, and even more esoteric uses like remote instrument
172 172 status aqcuired over the network. But now that IPython has a clean separation
173 173 between the kernel and the clients, the kernel has no prompt knowledge; prompts
174 174 are a frontend-side feature, and it should be even possible for different
175 175 frontends to display different prompts while interacting with the same kernel.
176 176
177 177 The kernel now provides the ability to retrieve data from the user's namespace
178 178 after the execution of the main ``code``, thanks to two fields in the
179 179 ``execute_request`` message:
180 180
181 181 - ``user_variables``: If only variables from the user's namespace are needed, a
182 182 list of variable names can be passed and a dict with these names as keys and
183 183 their :func:`repr()` as values will be returned.
184 184
185 185 - ``user_expressions``: For more complex expressions that require function
186 186 evaluations, a dict can be provided with string keys and arbitrary python
187 187 expressions as values. The return message will contain also a dict with the
188 188 same keys and the :func:`repr()` of the evaluated expressions as value.
189 189
190 190 With this information, frontends can display any status information they wish
191 191 in the form that best suits each frontend (a status line, a popup, inline for a
192 192 terminal, etc).
193 193
194 194 .. Note::
195 195
196 196 In order to obtain the current execution counter for the purposes of
197 197 displaying input prompts, frontends simply make an execution request with an
198 198 empty code string and ``silent=True``.
199 199
200 200 Execution semantics
201 201 ~~~~~~~~~~~~~~~~~~~
202 202
203 203 When the silent flag is false, the execution of use code consists of the
204 204 following phases (in silent mode, only the ``code`` field is executed):
205 205
206 206 1. Run the ``pre_runcode_hook``.
207 207
208 208 2. Execute the ``code`` field, see below for details.
209 209
210 210 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
211 211 computed. This ensures that any error in the latter don't harm the main
212 212 code execution.
213 213
214 214 4. Call any method registered with :meth:`register_post_execute`.
215 215
216 216 .. warning::
217 217
218 218 The API for running code before/after the main code block is likely to
219 219 change soon. Both the ``pre_runcode_hook`` and the
220 220 :meth:`register_post_execute` are susceptible to modification, as we find a
221 221 consistent model for both.
222 222
223 223 To understand how the ``code`` field is executed, one must know that Python
224 224 code can be compiled in one of three modes (controlled by the ``mode`` argument
225 225 to the :func:`compile` builtin):
226 226
227 227 *single*
228 228 Valid for a single interactive statement (though the source can contain
229 229 multiple lines, such as a for loop). When compiled in this mode, the
230 230 generated bytecode contains special instructions that trigger the calling of
231 231 :func:`sys.displayhook` for any expression in the block that returns a value.
232 232 This means that a single statement can actually produce multiple calls to
233 233 :func:`sys.displayhook`, if for example it contains a loop where each
234 234 iteration computes an unassigned expression would generate 10 calls::
235 235
236 236 for i in range(10):
237 237 i**2
238 238
239 239 *exec*
240 240 An arbitrary amount of source code, this is how modules are compiled.
241 241 :func:`sys.displayhook` is *never* implicitly called.
242 242
243 243 *eval*
244 244 A single expression that returns a value. :func:`sys.displayhook` is *never*
245 245 implicitly called.
246 246
247 247
248 248 The ``code`` field is split into individual blocks each of which is valid for
249 249 execution in 'single' mode, and then:
250 250
251 251 - If there is only a single block: it is executed in 'single' mode.
252 252
253 253 - If there is more than one block:
254 254
255 255 * if the last one is a single line long, run all but the last in 'exec' mode
256 256 and the very last one in 'single' mode. This makes it easy to type simple
257 257 expressions at the end to see computed values.
258 258
259 259 * if the last one is no more than two lines long, run all but the last in
260 260 'exec' mode and the very last one in 'single' mode. This makes it easy to
261 261 type simple expressions at the end to see computed values. - otherwise
262 262 (last one is also multiline), run all in 'exec' mode
263 263
264 264 * otherwise (last one is also multiline), run all in 'exec' mode as a single
265 265 unit.
266 266
267 267 Any error in retrieving the ``user_variables`` or evaluating the
268 268 ``user_expressions`` will result in a simple error message in the return fields
269 269 of the form::
270 270
271 271 [ERROR] ExceptionType: Exception message
272 272
273 273 The user can simply send the same variable name or expression for evaluation to
274 274 see a regular traceback.
275 275
276 276 Errors in any registered post_execute functions are also reported similarly,
277 277 and the failing function is removed from the post_execution set so that it does
278 278 not continue triggering failures.
279 279
280 280 Upon completion of the execution request, the kernel *always* sends a reply,
281 281 with a status code indicating what happened and additional data depending on
282 282 the outcome. See :ref:`below <execution_results>` for the possible return
283 283 codes and associated data.
284 284
285 285
286 286 Execution counter (old prompt number)
287 287 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288 288
289 289 The kernel has a single, monotonically increasing counter of all execution
290 290 requests that are made with ``silent=False``. This counter is used to populate
291 291 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
292 292 display it in some form to the user, which will typically (but not necessarily)
293 293 be done in the prompts. The value of this counter will be returned as the
294 294 ``execution_count`` field of all ``execute_reply`` messages.
295 295
296 296 .. _execution_results:
297 297
298 298 Execution results
299 299 ~~~~~~~~~~~~~~~~~
300 300
301 301 Message type: ``execute_reply``::
302 302
303 303 content = {
304 304 # One of: 'ok' OR 'error' OR 'abort'
305 305 'status' : str,
306 306
307 307 # The global kernel counter that increases by one with each non-silent
308 308 # executed request. This will typically be used by clients to display
309 309 # prompt numbers to the user. If the request was a silent one, this will
310 310 # be the current value of the counter in the kernel.
311 311 'execution_count' : int,
312 312 }
313 313
314 314 When status is 'ok', the following extra fields are present::
315 315
316 316 {
317 317 # The execution payload is a dict with string keys that may have been
318 318 # produced by the code being executed. It is retrieved by the kernel at
319 319 # the end of the execution and sent back to the front end, which can take
320 320 # action on it as needed. See main text for further details.
321 321 'payload' : dict,
322 322
323 323 # Results for the user_variables and user_expressions.
324 324 'user_variables' : dict,
325 325 'user_expressions' : dict,
326 326
327 327 # The kernel will often transform the input provided to it. If the
328 328 # '---->' transform had been applied, this is filled, otherwise it's the
329 329 # empty string. So transformations like magics don't appear here, only
330 330 # autocall ones.
331 331 'transformed_code' : str,
332 332 }
333 333
334 334 .. admonition:: Execution payloads
335 335
336 336 The notion of an 'execution payload' is different from a return value of a
337 337 given set of code, which normally is just displayed on the pyout stream
338 338 through the PUB socket. The idea of a payload is to allow special types of
339 339 code, typically magics, to populate a data container in the IPython kernel
340 340 that will be shipped back to the caller via this channel. The kernel will
341 341 have an API for this, probably something along the lines of::
342 342
343 343 ip.exec_payload_add(key, value)
344 344
345 345 though this API is still in the design stages. The data returned in this
346 346 payload will allow frontends to present special views of what just happened.
347 347
348 348
349 349 When status is 'error', the following extra fields are present::
350 350
351 351 {
352 352 'exc_name' : str, # Exception name, as a string
353 353 'exc_value' : str, # Exception value, as a string
354 354
355 355 # The traceback will contain a list of frames, represented each as a
356 356 # string. For now we'll stick to the existing design of ultraTB, which
357 357 # controls exception level of detail statefully. But eventually we'll
358 358 # want to grow into a model where more information is collected and
359 359 # packed into the traceback object, with clients deciding how little or
360 360 # how much of it to unpack. But for now, let's start with a simple list
361 361 # of strings, since that requires only minimal changes to ultratb as
362 362 # written.
363 363 'traceback' : list,
364 364 }
365 365
366 366
367 367 When status is 'abort', there are for now no additional data fields. This
368 368 happens when the kernel was interrupted by a signal.
369 369
370 370 Kernel attribute access
371 371 -----------------------
372 372
373 373 .. warning::
374 374
375 375 This part of the messaging spec is not actually implemented in the kernel
376 376 yet.
377 377
378 378 While this protocol does not specify full RPC access to arbitrary methods of
379 379 the kernel object, the kernel does allow read (and in some cases write) access
380 380 to certain attributes.
381 381
382 382 The policy for which attributes can be read is: any attribute of the kernel, or
383 383 its sub-objects, that belongs to a :class:`Configurable` object and has been
384 384 declared at the class-level with Traits validation, is in principle accessible
385 385 as long as its name does not begin with a leading underscore. The attribute
386 386 itself will have metadata indicating whether it allows remote read and/or write
387 387 access. The message spec follows for attribute read and write requests.
388 388
389 389 Message type: ``getattr_request``::
390 390
391 391 content = {
392 392 # The (possibly dotted) name of the attribute
393 393 'name' : str,
394 394 }
395 395
396 396 When a ``getattr_request`` fails, there are two possible error types:
397 397
398 398 - AttributeError: this type of error was raised when trying to access the
399 399 given name by the kernel itself. This means that the attribute likely
400 400 doesn't exist.
401 401
402 402 - AccessError: the attribute exists but its value is not readable remotely.
403 403
404 404
405 405 Message type: ``getattr_reply``::
406 406
407 407 content = {
408 408 # One of ['ok', 'AttributeError', 'AccessError'].
409 409 'status' : str,
410 410 # If status is 'ok', a JSON object.
411 411 'value' : object,
412 412 }
413 413
414 414 Message type: ``setattr_request``::
415 415
416 416 content = {
417 417 # The (possibly dotted) name of the attribute
418 418 'name' : str,
419 419
420 420 # A JSON-encoded object, that will be validated by the Traits
421 421 # information in the kernel
422 422 'value' : object,
423 423 }
424 424
425 425 When a ``setattr_request`` fails, there are also two possible error types with
426 426 similar meanings as those of the ``getattr_request`` case, but for writing.
427 427
428 428 Message type: ``setattr_reply``::
429 429
430 430 content = {
431 431 # One of ['ok', 'AttributeError', 'AccessError'].
432 432 'status' : str,
433 433 }
434 434
435 435
436 436
437 437 Object information
438 438 ------------------
439 439
440 440 One of IPython's most used capabilities is the introspection of Python objects
441 441 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
442 442 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
443 443 enough that it warrants an explicit message type, especially because frontends
444 444 may want to get object information in response to user keystrokes (like Tab or
445 445 F1) besides from the user explicitly typing code like ``x??``.
446 446
447 447 Message type: ``object_info_request``::
448 448
449 449 content = {
450 450 # The (possibly dotted) name of the object to be searched in all
451 451 # relevant namespaces
452 452 'name' : str,
453 453
454 454 # The level of detail desired. The default (0) is equivalent to typing
455 455 # 'x?' at the prompt, 1 is equivalent to 'x??'.
456 456 'detail_level' : int,
457 457 }
458 458
459 459 The returned information will be a dictionary with keys very similar to the
460 460 field names that IPython prints at the terminal.
461 461
462 462 Message type: ``object_info_reply``::
463 463
464 464 content = {
465 465 # The name the object was requested under
466 466 'name' : str,
467 467
468 468 # Boolean flag indicating whether the named object was found or not. If
469 469 # it's false, all other fields will be empty.
470 470 'found' : bool,
471 471
472 472 # Flags for magics and system aliases
473 473 'ismagic' : bool,
474 474 'isalias' : bool,
475 475
476 476 # The name of the namespace where the object was found ('builtin',
477 477 # 'magics', 'alias', 'interactive', etc.)
478 478 'namespace' : str,
479 479
480 480 # The type name will be type.__name__ for normal Python objects, but it
481 481 # can also be a string like 'Magic function' or 'System alias'
482 482 'type_name' : str,
483 483
484 484 # The string form of the object, possibly truncated for length if
485 485 # detail_level is 0
486 486 'string_form' : str,
487 487
488 488 # For objects with a __class__ attribute this will be set
489 489 'base_class' : str,
490 490
491 491 # For objects with a __len__ attribute this will be set
492 492 'length' : int,
493 493
494 494 # If the object is a function, class or method whose file we can find,
495 495 # we give its full path
496 496 'file' : str,
497 497
498 498 # For pure Python callable objects, we can reconstruct the object
499 499 # definition line which provides its call signature. For convenience this
500 500 # is returned as a single 'definition' field, but below the raw parts that
501 501 # compose it are also returned as the argspec field.
502 502 'definition' : str,
503 503
504 504 # The individual parts that together form the definition string. Clients
505 505 # with rich display capabilities may use this to provide a richer and more
506 506 # precise representation of the definition line (e.g. by highlighting
507 507 # arguments based on the user's cursor position). For non-callable
508 508 # objects, this field is empty.
509 509 'argspec' : { # The names of all the arguments
510 510 args : list,
511 511 # The name of the varargs (*args), if any
512 512 varargs : str,
513 513 # The name of the varkw (**kw), if any
514 514 varkw : str,
515 515 # The values (as strings) of all default arguments. Note
516 516 # that these must be matched *in reverse* with the 'args'
517 517 # list above, since the first positional args have no default
518 518 # value at all.
519 519 defaults : list,
520 520 },
521 521
522 522 # For instances, provide the constructor signature (the definition of
523 523 # the __init__ method):
524 524 'init_definition' : str,
525 525
526 526 # Docstrings: for any object (function, method, module, package) with a
527 527 # docstring, we show it. But in addition, we may provide additional
528 528 # docstrings. For example, for instances we will show the constructor
529 529 # and class docstrings as well, if available.
530 530 'docstring' : str,
531 531
532 532 # For instances, provide the constructor and class docstrings
533 533 'init_docstring' : str,
534 534 'class_docstring' : str,
535 535
536 536 # If it's a callable object whose call method has a separate docstring and
537 537 # definition line:
538 538 'call_def' : str,
539 539 'call_docstring' : str,
540 540
541 541 # If detail_level was 1, we also try to find the source code that
542 542 # defines the object, if possible. The string 'None' will indicate
543 543 # that no source was found.
544 544 'source' : str,
545 545 }
546 546 '
547 547
548 548 Complete
549 549 --------
550 550
551 551 Message type: ``complete_request``::
552 552
553 553 content = {
554 554 # The text to be completed, such as 'a.is'
555 555 'text' : str,
556 556
557 557 # The full line, such as 'print a.is'. This allows completers to
558 558 # make decisions that may require information about more than just the
559 559 # current word.
560 560 'line' : str,
561 561
562 562 # The entire block of text where the line is. This may be useful in the
563 563 # case of multiline completions where more context may be needed. Note: if
564 564 # in practice this field proves unnecessary, remove it to lighten the
565 565 # messages.
566 566
567 567 'block' : str,
568 568
569 569 # The position of the cursor where the user hit 'TAB' on the line.
570 570 'cursor_pos' : int,
571 571 }
572 572
573 573 Message type: ``complete_reply``::
574 574
575 575 content = {
576 576 # The list of all matches to the completion request, such as
577 577 # ['a.isalnum', 'a.isalpha'] for the above example.
578 578 'matches' : list
579 579 }
580 580
581 581
582 582 History
583 583 -------
584 584
585 585 For clients to explicitly request history from a kernel. The kernel has all
586 586 the actual execution history stored in a single location, so clients can
587 587 request it from the kernel when needed.
588 588
589 589 Message type: ``history_request``::
590 590
591 591 content = {
592 592
593 593 # If True, also return output history in the resulting dict.
594 594 'output' : bool,
595 595
596 596 # If True, return the raw input history, else the transformed input.
597 597 'raw' : bool,
598 598
599 # This parameter can be one of: A number, a pair of numbers, None
600 # If not given, last 40 are returned.
601 # - number n: return the last n entries.
602 # - pair n1, n2: return entries in the range(n1, n2).
603 # - None: return all history
604 'index' : n or (n1, n2) or None,
599 # So far, this can be 'range', 'tail' or 'search'.
600 'hist_access_type' : str,
601
602 # If hist_access_type is 'range', get a range of input cells. session can
603 # be a positive session number, or a negative number to count back from
604 # the current session.
605 'session' : int,
606 # start and stop are line numbers within that session.
607 'start' : int,
608 'stop' : int,
609
610 # If hist_access_type is 'tail', get the last n cells.
611 'n' : int,
612
613 # If hist_access_type is 'search', get cells matching the specified glob
614 # pattern (with * and ? as wildcards).
615 'pattern' : str,
616
605 617 }
606 618
607 619 Message type: ``history_reply``::
608 620
609 621 content = {
610 # A dict with prompt numbers as keys and either (input, output) or input
611 # as the value depending on whether output was True or False,
612 # respectively.
613 'history' : dict,
622 # A list of 3 tuples, either:
623 # (session, line_number, input) or
624 # (session, line_number, (input, output)),
625 # depending on whether output was False or True, respectively.
626 'history' : list,
614 627 }
615 628
616 629
617 630 Connect
618 631 -------
619 632
620 633 When a client connects to the request/reply socket of the kernel, it can issue
621 634 a connect request to get basic information about the kernel, such as the ports
622 635 the other ZeroMQ sockets are listening on. This allows clients to only have
623 636 to know about a single port (the XREQ/XREP channel) to connect to a kernel.
624 637
625 638 Message type: ``connect_request``::
626 639
627 640 content = {
628 641 }
629 642
630 643 Message type: ``connect_reply``::
631 644
632 645 content = {
633 646 'xrep_port' : int # The port the XREP socket is listening on.
634 647 'pub_port' : int # The port the PUB socket is listening on.
635 648 'req_port' : int # The port the REQ socket is listening on.
636 649 'hb_port' : int # The port the heartbeat socket is listening on.
637 650 }
638 651
639 652
640 653
641 654 Kernel shutdown
642 655 ---------------
643 656
644 657 The clients can request the kernel to shut itself down; this is used in
645 658 multiple cases:
646 659
647 660 - when the user chooses to close the client application via a menu or window
648 661 control.
649 662 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
650 663 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
651 664 IPythonQt client) to force a kernel restart to get a clean kernel without
652 665 losing client-side state like history or inlined figures.
653 666
654 667 The client sends a shutdown request to the kernel, and once it receives the
655 668 reply message (which is otherwise empty), it can assume that the kernel has
656 669 completed shutdown safely.
657 670
658 671 Upon their own shutdown, client applications will typically execute a last
659 672 minute sanity check and forcefully terminate any kernel that is still alive, to
660 673 avoid leaving stray processes in the user's machine.
661 674
662 675 For both shutdown request and reply, there is no actual content that needs to
663 676 be sent, so the content dict is empty.
664 677
665 678 Message type: ``shutdown_request``::
666 679
667 680 content = {
668 681 'restart' : bool # whether the shutdown is final, or precedes a restart
669 682 }
670 683
671 684 Message type: ``shutdown_reply``::
672 685
673 686 content = {
674 687 'restart' : bool # whether the shutdown is final, or precedes a restart
675 688 }
676 689
677 690 .. Note::
678 691
679 692 When the clients detect a dead kernel thanks to inactivity on the heartbeat
680 693 socket, they simply send a forceful process termination signal, since a dead
681 694 process is unlikely to respond in any useful way to messages.
682 695
683 696
684 697 Messages on the PUB/SUB socket
685 698 ==============================
686 699
687 700 Streams (stdout, stderr, etc)
688 701 ------------------------------
689 702
690 703 Message type: ``stream``::
691 704
692 705 content = {
693 706 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
694 707 'name' : str,
695 708
696 709 # The data is an arbitrary string to be written to that stream
697 710 'data' : str,
698 711 }
699 712
700 713 When a kernel receives a raw_input call, it should also broadcast it on the pub
701 714 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
702 715 to monitor/display kernel interactions and possibly replay them to their user
703 716 or otherwise expose them.
704 717
705 718 Display Data
706 719 ------------
707 720
708 721 This type of message is used to bring back data that should be diplayed (text,
709 722 html, svg, etc.) in the frontends. This data is published to all frontends.
710 723 Each message can have multiple representations of the data; it is up to the
711 724 frontend to decide which to use and how. A single message should contain all
712 725 possible representations of the same information. Each representation should
713 726 be a JSON'able data structure, and should be a valid MIME type.
714 727
715 728 Some questions remain about this design:
716 729
717 730 * Do we use this message type for pyout/displayhook? Probably not, because
718 731 the displayhook also has to handle the Out prompt display. On the other hand
719 732 we could put that information into the metadata secion.
720 733
721 734 Message type: ``display_data``::
722 735
723 736 content = {
724 737
725 738 # Who create the data
726 739 'source' : str,
727 740
728 741 # The data dict contains key/value pairs, where the kids are MIME
729 742 # types and the values are the raw data of the representation in that
730 743 # format. The data dict must minimally contain the ``text/plain``
731 744 # MIME type which is used as a backup representation.
732 745 'data' : dict,
733 746
734 747 # Any metadata that describes the data
735 748 'metadata' : dict
736 749 }
737 750
738 751 Python inputs
739 752 -------------
740 753
741 754 These messages are the re-broadcast of the ``execute_request``.
742 755
743 756 Message type: ``pyin``::
744 757
745 758 content = {
746 759 'code' : str # Source code to be executed, one or more lines
747 760 }
748 761
749 762 Python outputs
750 763 --------------
751 764
752 765 When Python produces output from code that has been compiled in with the
753 766 'single' flag to :func:`compile`, any expression that produces a value (such as
754 767 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
755 768 this value whatever it wants. The default behavior of ``sys.displayhook`` in
756 769 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
757 770 the value as long as it is not ``None`` (which isn't printed at all). In our
758 771 case, the kernel instantiates as ``sys.displayhook`` an object which has
759 772 similar behavior, but which instead of printing to stdout, broadcasts these
760 773 values as ``pyout`` messages for clients to display appropriately.
761 774
762 775 IPython's displayhook can handle multiple simultaneous formats depending on its
763 776 configuration. The default pretty-printed repr text is always given with the
764 777 ``data`` entry in this message. Any other formats are provided in the
765 778 ``extra_formats`` list. Frontends are free to display any or all of these
766 779 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
767 780 string, a type string, and the data. The ID is unique to the formatter
768 781 implementation that created the data. Frontends will typically ignore the ID
769 782 unless if it has requested a particular formatter. The type string tells the
770 783 frontend how to interpret the data. It is often, but not always a MIME type.
771 784 Frontends should ignore types that it does not understand. The data itself is
772 785 any JSON object and depends on the format. It is often, but not always a string.
773 786
774 787 Message type: ``pyout``::
775 788
776 789 content = {
777 790
778 791 # The counter for this execution is also provided so that clients can
779 792 # display it, since IPython automatically creates variables called _N
780 793 # (for prompt N).
781 794 'execution_count' : int,
782 795
783 796 # The data dict contains key/value pairs, where the kids are MIME
784 797 # types and the values are the raw data of the representation in that
785 798 # format. The data dict must minimally contain the ``text/plain``
786 799 # MIME type which is used as a backup representation.
787 800 'data' : dict,
788 801
789 802 }
790 803
791 804 Python errors
792 805 -------------
793 806
794 807 When an error occurs during code execution
795 808
796 809 Message type: ``pyerr``::
797 810
798 811 content = {
799 812 # Similar content to the execute_reply messages for the 'error' case,
800 813 # except the 'status' field is omitted.
801 814 }
802 815
803 816 Kernel status
804 817 -------------
805 818
806 819 This message type is used by frontends to monitor the status of the kernel.
807 820
808 821 Message type: ``status``::
809 822
810 823 content = {
811 824 # When the kernel starts to execute code, it will enter the 'busy'
812 825 # state and when it finishes, it will enter the 'idle' state.
813 826 execution_state : ('busy', 'idle')
814 827 }
815 828
816 829 Kernel crashes
817 830 --------------
818 831
819 832 When the kernel has an unexpected exception, caught by the last-resort
820 833 sys.excepthook, we should broadcast the crash handler's output before exiting.
821 834 This will allow clients to notice that a kernel died, inform the user and
822 835 propose further actions.
823 836
824 837 Message type: ``crash``::
825 838
826 839 content = {
827 840 # Similarly to the 'error' case for execute_reply messages, this will
828 841 # contain exc_name, exc_type and traceback fields.
829 842
830 843 # An additional field with supplementary information such as where to
831 844 # send the crash message
832 845 'info' : str,
833 846 }
834 847
835 848
836 849 Future ideas
837 850 ------------
838 851
839 852 Other potential message types, currently unimplemented, listed below as ideas.
840 853
841 854 Message type: ``file``::
842 855
843 856 content = {
844 857 'path' : 'cool.jpg',
845 858 'mimetype' : str,
846 859 'data' : str,
847 860 }
848 861
849 862
850 863 Messages on the REQ/REP socket
851 864 ==============================
852 865
853 866 This is a socket that goes in the opposite direction: from the kernel to a
854 867 *single* frontend, and its purpose is to allow ``raw_input`` and similar
855 868 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
856 869 client. For now we will keep these messages as simple as possible, since they
857 870 basically only mean to convey the ``raw_input(prompt)`` call.
858 871
859 872 Message type: ``input_request``::
860 873
861 874 content = { 'prompt' : str }
862 875
863 876 Message type: ``input_reply``::
864 877
865 878 content = { 'value' : str }
866 879
867 880 .. Note::
868 881
869 882 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
870 883 practice the kernel should behave like an interactive program. When a
871 884 program is opened on the console, the keyboard effectively takes over the
872 885 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
873 886 Since the IPython kernel effectively behaves like a console program (albeit
874 887 one whose "keyboard" is actually living in a separate process and
875 888 transported over the zmq connection), raw ``stdin`` isn't expected to be
876 889 available.
877 890
878 891
879 892 Heartbeat for kernels
880 893 =====================
881 894
882 895 Initially we had considered using messages like those above over ZMQ for a
883 896 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
884 897 alive at all, even if it may be busy executing user code). But this has the
885 898 problem that if the kernel is locked inside extension code, it wouldn't execute
886 899 the python heartbeat code. But it turns out that we can implement a basic
887 900 heartbeat with pure ZMQ, without using any Python messaging at all.
888 901
889 902 The monitor sends out a single zmq message (right now, it is a str of the
890 903 monitor's lifetime in seconds), and gets the same message right back, prefixed
891 904 with the zmq identity of the XREQ socket in the heartbeat process. This can be
892 905 a uuid, or even a full message, but there doesn't seem to be a need for packing
893 906 up a message when the sender and receiver are the exact same Python object.
894 907
895 908 The model is this::
896 909
897 910 monitor.send(str(self.lifetime)) # '1.2345678910'
898 911
899 912 and the monitor receives some number of messages of the form::
900 913
901 914 ['uuid-abcd-dead-beef', '1.2345678910']
902 915
903 916 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
904 917 the rest is the message sent by the monitor. No Python code ever has any
905 918 access to the message between the monitor's send, and the monitor's recv.
906 919
907 920
908 921 ToDo
909 922 ====
910 923
911 924 Missing things include:
912 925
913 926 * Important: finish thinking through the payload concept and API.
914 927
915 928 * Important: ensure that we have a good solution for magics like %edit. It's
916 929 likely that with the payload concept we can build a full solution, but not
917 930 100% clear yet.
918 931
919 932 * Finishing the details of the heartbeat protocol.
920 933
921 934 * Signal handling: specify what kind of information kernel should broadcast (or
922 935 not) when it receives signals.
923 936
924 937 .. include:: ../links.rst
General Comments 0
You need to be logged in to leave comments. Login now