##// END OF EJS Templates
Added kernel shutdown support: messaging spec, zmq and client code ready....
Fernando Perez -
Show More
@@ -1,483 +1,491 b''
1 1 # Standard library imports
2 2 from collections import namedtuple
3 3 import signal
4 4 import sys
5 5
6 6 # System library imports
7 7 from pygments.lexers import PythonLexer
8 8 from PyQt4 import QtCore, QtGui
9 9
10 10 # Local imports
11 11 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
12 12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 13 from IPython.utils.io import raw_print
14 14 from IPython.utils.traitlets import Bool
15 15 from bracket_matcher import BracketMatcher
16 16 from call_tip_widget import CallTipWidget
17 17 from completion_lexer import CompletionLexer
18 18 from console_widget import HistoryConsoleWidget
19 19 from pygments_highlighter import PygmentsHighlighter
20 20
21 21
22 22 class FrontendHighlighter(PygmentsHighlighter):
23 23 """ A PygmentsHighlighter that can be turned on and off and that ignores
24 24 prompts.
25 25 """
26 26
27 27 def __init__(self, frontend):
28 28 super(FrontendHighlighter, self).__init__(frontend._control.document())
29 29 self._current_offset = 0
30 30 self._frontend = frontend
31 31 self.highlighting_on = False
32 32
33 33 def highlightBlock(self, qstring):
34 34 """ Highlight a block of text. Reimplemented to highlight selectively.
35 35 """
36 36 if not self.highlighting_on:
37 37 return
38 38
39 39 # The input to this function is unicode string that may contain
40 40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
41 41 # the string as plain text so we can compare it.
42 42 current_block = self.currentBlock()
43 43 string = self._frontend._get_block_plain_text(current_block)
44 44
45 45 # Decide whether to check for the regular or continuation prompt.
46 46 if current_block.contains(self._frontend._prompt_pos):
47 47 prompt = self._frontend._prompt
48 48 else:
49 49 prompt = self._frontend._continuation_prompt
50 50
51 51 # Don't highlight the part of the string that contains the prompt.
52 52 if string.startswith(prompt):
53 53 self._current_offset = len(prompt)
54 54 qstring.remove(0, len(prompt))
55 55 else:
56 56 self._current_offset = 0
57 57
58 58 PygmentsHighlighter.highlightBlock(self, qstring)
59 59
60 60 def rehighlightBlock(self, block):
61 61 """ Reimplemented to temporarily enable highlighting if disabled.
62 62 """
63 63 old = self.highlighting_on
64 64 self.highlighting_on = True
65 65 super(FrontendHighlighter, self).rehighlightBlock(block)
66 66 self.highlighting_on = old
67 67
68 68 def setFormat(self, start, count, format):
69 69 """ Reimplemented to highlight selectively.
70 70 """
71 71 start += self._current_offset
72 72 PygmentsHighlighter.setFormat(self, start, count, format)
73 73
74 74
75 75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 76 """ A Qt frontend for a generic Python kernel.
77 77 """
78 78
79 79 # An option and corresponding signal for overriding the default kernel
80 80 # interrupt behavior.
81 81 custom_interrupt = Bool(False)
82 82 custom_interrupt_requested = QtCore.pyqtSignal()
83 83
84 84 # An option and corresponding signals for overriding the default kernel
85 85 # restart behavior.
86 86 custom_restart = Bool(False)
87 87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
88 88 custom_restart_requested = QtCore.pyqtSignal()
89 89
90 90 # Emitted when an 'execute_reply' has been received from the kernel and
91 91 # processed by the FrontendWidget.
92 92 executed = QtCore.pyqtSignal(object)
93 93
94 94 # Emitted when an exit request has been received from the kernel.
95 95 exit_requested = QtCore.pyqtSignal()
96 96
97 97 # Protected class variables.
98 98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
99 99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
100 100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
101 101 _input_splitter_class = InputSplitter
102 102
103 103 #---------------------------------------------------------------------------
104 104 # 'object' interface
105 105 #---------------------------------------------------------------------------
106 106
107 107 def __init__(self, *args, **kw):
108 108 super(FrontendWidget, self).__init__(*args, **kw)
109 109
110 110 # FrontendWidget protected variables.
111 111 self._bracket_matcher = BracketMatcher(self._control)
112 112 self._call_tip_widget = CallTipWidget(self._control)
113 113 self._completion_lexer = CompletionLexer(PythonLexer())
114 114 self._hidden = False
115 115 self._highlighter = FrontendHighlighter(self)
116 116 self._input_splitter = self._input_splitter_class(input_mode='block')
117 117 self._kernel_manager = None
118 118 self._possible_kernel_restart = False
119 119 self._request_info = {}
120 120
121 121 # Configure the ConsoleWidget.
122 122 self.tab_width = 4
123 123 self._set_continuation_prompt('... ')
124 124
125 125 # Connect signal handlers.
126 126 document = self._control.document()
127 127 document.contentsChange.connect(self._document_contents_change)
128 128
129 129 #---------------------------------------------------------------------------
130 130 # 'ConsoleWidget' public interface
131 131 #---------------------------------------------------------------------------
132 132
133 133 def copy(self):
134 134 """ Copy the currently selected text to the clipboard, removing prompts.
135 135 """
136 136 text = str(self._control.textCursor().selection().toPlainText())
137 137 if text:
138 138 # Remove prompts.
139 139 lines = map(transform_classic_prompt, text.splitlines())
140 140 text = '\n'.join(lines)
141 141 # Expand tabs so that we respect PEP-8.
142 142 QtGui.QApplication.clipboard().setText(text.expandtabs(4))
143 143
144 144 #---------------------------------------------------------------------------
145 145 # 'ConsoleWidget' abstract interface
146 146 #---------------------------------------------------------------------------
147 147
148 148 def _is_complete(self, source, interactive):
149 149 """ Returns whether 'source' can be completely processed and a new
150 150 prompt created. When triggered by an Enter/Return key press,
151 151 'interactive' is True; otherwise, it is False.
152 152 """
153 153 complete = self._input_splitter.push(source.expandtabs(4))
154 154 if interactive:
155 155 complete = not self._input_splitter.push_accepts_more()
156 156 return complete
157 157
158 158 def _execute(self, source, hidden):
159 159 """ Execute 'source'. If 'hidden', do not show any output.
160 160
161 161 See parent class :meth:`execute` docstring for full details.
162 162 """
163 163 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
164 164 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
165 165 self._hidden = hidden
166 166
167 167 def _prompt_started_hook(self):
168 168 """ Called immediately after a new prompt is displayed.
169 169 """
170 170 if not self._reading:
171 171 self._highlighter.highlighting_on = True
172 172
173 173 def _prompt_finished_hook(self):
174 174 """ Called immediately after a prompt is finished, i.e. when some input
175 175 will be processed and a new prompt displayed.
176 176 """
177 177 if not self._reading:
178 178 self._highlighter.highlighting_on = False
179 179
180 180 def _tab_pressed(self):
181 181 """ Called when the tab key is pressed. Returns whether to continue
182 182 processing the event.
183 183 """
184 184 # Perform tab completion if:
185 185 # 1) The cursor is in the input buffer.
186 186 # 2) There is a non-whitespace character before the cursor.
187 187 text = self._get_input_buffer_cursor_line()
188 188 if text is None:
189 189 return False
190 190 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
191 191 if complete:
192 192 self._complete()
193 193 return not complete
194 194
195 195 #---------------------------------------------------------------------------
196 196 # 'ConsoleWidget' protected interface
197 197 #---------------------------------------------------------------------------
198 198
199 199 def _event_filter_console_keypress(self, event):
200 200 """ Reimplemented to allow execution interruption.
201 201 """
202 202 key = event.key()
203 203 if self._control_key_down(event.modifiers(), include_command=False):
204 204 if key == QtCore.Qt.Key_C and self._executing:
205 205 self.interrupt_kernel()
206 206 return True
207 207 elif key == QtCore.Qt.Key_Period:
208 208 message = 'Are you sure you want to restart the kernel?'
209 self.restart_kernel(message)
209 self.restart_kernel(message, instant_death=False)
210 210 return True
211 211 return super(FrontendWidget, self)._event_filter_console_keypress(event)
212 212
213 213 def _insert_continuation_prompt(self, cursor):
214 214 """ Reimplemented for auto-indentation.
215 215 """
216 216 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
217 217 spaces = self._input_splitter.indent_spaces
218 218 cursor.insertText('\t' * (spaces / self.tab_width))
219 219 cursor.insertText(' ' * (spaces % self.tab_width))
220 220
221 221 #---------------------------------------------------------------------------
222 222 # 'BaseFrontendMixin' abstract interface
223 223 #---------------------------------------------------------------------------
224 224
225 225 def _handle_complete_reply(self, rep):
226 226 """ Handle replies for tab completion.
227 227 """
228 228 cursor = self._get_cursor()
229 229 info = self._request_info.get('complete')
230 230 if info and info.id == rep['parent_header']['msg_id'] and \
231 231 info.pos == cursor.position():
232 232 text = '.'.join(self._get_context())
233 233 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
234 234 self._complete_with_items(cursor, rep['content']['matches'])
235 235
236 236 def _handle_execute_reply(self, msg):
237 237 """ Handles replies for code execution.
238 238 """
239 239 info = self._request_info.get('execute')
240 240 if info and info.id == msg['parent_header']['msg_id'] and \
241 241 info.kind == 'user' and not self._hidden:
242 242 # Make sure that all output from the SUB channel has been processed
243 243 # before writing a new prompt.
244 244 self.kernel_manager.sub_channel.flush()
245 245
246 246 content = msg['content']
247 247 status = content['status']
248 248 if status == 'ok':
249 249 self._process_execute_ok(msg)
250 250 elif status == 'error':
251 251 self._process_execute_error(msg)
252 252 elif status == 'abort':
253 253 self._process_execute_abort(msg)
254 254
255 255 self._show_interpreter_prompt_for_reply(msg)
256 256 self.executed.emit(msg)
257 257
258 258 def _handle_input_request(self, msg):
259 259 """ Handle requests for raw_input.
260 260 """
261 261 if self._hidden:
262 262 raise RuntimeError('Request for raw input during hidden execution.')
263 263
264 264 # Make sure that all output from the SUB channel has been processed
265 265 # before entering readline mode.
266 266 self.kernel_manager.sub_channel.flush()
267 267
268 268 def callback(line):
269 269 self.kernel_manager.rep_channel.input(line)
270 270 self._readline(msg['content']['prompt'], callback=callback)
271 271
272 272 def _handle_kernel_died(self, since_last_heartbeat):
273 273 """ Handle the kernel's death by asking if the user wants to restart.
274 274 """
275 275 message = 'The kernel heartbeat has been inactive for %.2f ' \
276 276 'seconds. Do you want to restart the kernel? You may ' \
277 277 'first want to check the network connection.' % \
278 278 since_last_heartbeat
279 279 if self.custom_restart:
280 280 self.custom_restart_kernel_died.emit(since_last_heartbeat)
281 281 else:
282 self.restart_kernel(message)
282 self.restart_kernel(message, instant_death=True)
283 283
284 284 def _handle_object_info_reply(self, rep):
285 285 """ Handle replies for call tips.
286 286 """
287 287 cursor = self._get_cursor()
288 288 info = self._request_info.get('call_tip')
289 289 if info and info.id == rep['parent_header']['msg_id'] and \
290 290 info.pos == cursor.position():
291 291 doc = rep['content']['docstring']
292 292 if doc:
293 293 self._call_tip_widget.show_docstring(doc)
294 294
295 295 def _handle_pyout(self, msg):
296 296 """ Handle display hook output.
297 297 """
298 298 if not self._hidden and self._is_from_this_session(msg):
299 299 self._append_plain_text(msg['content']['data'] + '\n')
300 300
301 301 def _handle_stream(self, msg):
302 302 """ Handle stdout, stderr, and stdin.
303 303 """
304 304 if not self._hidden and self._is_from_this_session(msg):
305 305 self._append_plain_text(msg['content']['data'])
306 306 self._control.moveCursor(QtGui.QTextCursor.End)
307 307
308 308 def _started_channels(self):
309 309 """ Called when the KernelManager channels have started listening or
310 310 when the frontend is assigned an already listening KernelManager.
311 311 """
312 312 self._control.clear()
313 313 self._append_plain_text(self._get_banner())
314 314 self._show_interpreter_prompt()
315 315
316 316 def _stopped_channels(self):
317 317 """ Called when the KernelManager channels have stopped listening or
318 318 when a listening KernelManager is removed from the frontend.
319 319 """
320 320 self._executing = self._reading = False
321 321 self._highlighter.highlighting_on = False
322 322
323 323 #---------------------------------------------------------------------------
324 324 # 'FrontendWidget' public interface
325 325 #---------------------------------------------------------------------------
326 326
327 327 def execute_file(self, path, hidden=False):
328 328 """ Attempts to execute file with 'path'. If 'hidden', no output is
329 329 shown.
330 330 """
331 331 self.execute('execfile("%s")' % path, hidden=hidden)
332 332
333 333 def interrupt_kernel(self):
334 334 """ Attempts to interrupt the running kernel.
335 335 """
336 336 if self.custom_interrupt:
337 337 self.custom_interrupt_requested.emit()
338 338 elif self.kernel_manager.has_kernel:
339 339 self.kernel_manager.signal_kernel(signal.SIGINT)
340 340 else:
341 341 self._append_plain_text('Kernel process is either remote or '
342 342 'unspecified. Cannot interrupt.\n')
343 343
344 def restart_kernel(self, message):
344 def restart_kernel(self, message, instant_death=False):
345 345 """ Attempts to restart the running kernel.
346 346 """
347 # FIXME: instant_death should be configurable via a checkbox in the
348 # dialog. Right now at least the heartbeat path sets it to True and
349 # the manual restart to False. But those should just be the
350 # pre-selected states of a checkbox that the user could override if so
351 # desired. But I don't know enough Qt to go implementing the checkbox
352 # now.
353
347 354 # We want to make sure that if this dialog is already happening, that
348 355 # other signals don't trigger it again. This can happen when the
349 356 # kernel_died heartbeat signal is emitted and the user is slow to
350 357 # respond to the dialog.
351 358 if not self._possible_kernel_restart:
352 359 if self.custom_restart:
353 360 self.custom_restart_requested.emit()
354 361 elif self.kernel_manager.has_kernel:
355 362 # Setting this to True will prevent this logic from happening
356 363 # again until the current pass is completed.
357 364 self._possible_kernel_restart = True
358 365 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
359 366 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
360 367 message, buttons)
361 368 if result == QtGui.QMessageBox.Yes:
362 369 try:
363 self.kernel_manager.restart_kernel()
370 self.kernel_manager.restart_kernel(
371 instant_death=instant_death)
364 372 except RuntimeError:
365 373 message = 'Kernel started externally. Cannot restart.\n'
366 374 self._append_plain_text(message)
367 375 else:
368 376 self._stopped_channels()
369 377 self._append_plain_text('Kernel restarting...\n')
370 378 self._show_interpreter_prompt()
371 379 # This might need to be moved to another location?
372 380 self._possible_kernel_restart = False
373 381 else:
374 382 self._append_plain_text('Kernel process is either remote or '
375 383 'unspecified. Cannot restart.\n')
376 384
377 385 #---------------------------------------------------------------------------
378 386 # 'FrontendWidget' protected interface
379 387 #---------------------------------------------------------------------------
380 388
381 389 def _call_tip(self):
382 390 """ Shows a call tip, if appropriate, at the current cursor location.
383 391 """
384 392 # Decide if it makes sense to show a call tip
385 393 cursor = self._get_cursor()
386 394 cursor.movePosition(QtGui.QTextCursor.Left)
387 395 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
388 396 return False
389 397 context = self._get_context(cursor)
390 398 if not context:
391 399 return False
392 400
393 401 # Send the metadata request to the kernel
394 402 name = '.'.join(context)
395 403 msg_id = self.kernel_manager.xreq_channel.object_info(name)
396 404 pos = self._get_cursor().position()
397 405 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
398 406 return True
399 407
400 408 def _complete(self):
401 409 """ Performs completion at the current cursor location.
402 410 """
403 411 context = self._get_context()
404 412 if context:
405 413 # Send the completion request to the kernel
406 414 msg_id = self.kernel_manager.xreq_channel.complete(
407 415 '.'.join(context), # text
408 416 self._get_input_buffer_cursor_line(), # line
409 417 self._get_input_buffer_cursor_column(), # cursor_pos
410 418 self.input_buffer) # block
411 419 pos = self._get_cursor().position()
412 420 info = self._CompletionRequest(msg_id, pos)
413 421 self._request_info['complete'] = info
414 422
415 423 def _get_banner(self):
416 424 """ Gets a banner to display at the beginning of a session.
417 425 """
418 426 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
419 427 '"license" for more information.'
420 428 return banner % (sys.version, sys.platform)
421 429
422 430 def _get_context(self, cursor=None):
423 431 """ Gets the context for the specified cursor (or the current cursor
424 432 if none is specified).
425 433 """
426 434 if cursor is None:
427 435 cursor = self._get_cursor()
428 436 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
429 437 QtGui.QTextCursor.KeepAnchor)
430 438 text = str(cursor.selection().toPlainText())
431 439 return self._completion_lexer.get_context(text)
432 440
433 441 def _process_execute_abort(self, msg):
434 442 """ Process a reply for an aborted execution request.
435 443 """
436 444 self._append_plain_text("ERROR: execution aborted\n")
437 445
438 446 def _process_execute_error(self, msg):
439 447 """ Process a reply for an execution request that resulted in an error.
440 448 """
441 449 content = msg['content']
442 450 traceback = ''.join(content['traceback'])
443 451 self._append_plain_text(traceback)
444 452
445 453 def _process_execute_ok(self, msg):
446 454 """ Process a reply for a successful execution equest.
447 455 """
448 456 payload = msg['content']['payload']
449 457 for item in payload:
450 458 if not self._process_execute_payload(item):
451 459 warning = 'Warning: received unknown payload of type %s'
452 460 raw_print(warning % repr(item['source']))
453 461
454 462 def _process_execute_payload(self, item):
455 463 """ Process a single payload item from the list of payload items in an
456 464 execution reply. Returns whether the payload was handled.
457 465 """
458 466 # The basic FrontendWidget doesn't handle payloads, as they are a
459 467 # mechanism for going beyond the standard Python interpreter model.
460 468 return False
461 469
462 470 def _show_interpreter_prompt(self):
463 471 """ Shows a prompt for the interpreter.
464 472 """
465 473 self._show_prompt('>>> ')
466 474
467 475 def _show_interpreter_prompt_for_reply(self, msg):
468 476 """ Shows a prompt for the interpreter given an 'execute_reply' message.
469 477 """
470 478 self._show_interpreter_prompt()
471 479
472 480 #------ Signal handlers ----------------------------------------------------
473 481
474 482 def _document_contents_change(self, position, removed, added):
475 483 """ Called whenever the document's content changes. Display a call tip
476 484 if appropriate.
477 485 """
478 486 # Calculate where the cursor should be *after* the change:
479 487 position += added
480 488
481 489 document = self._control.document()
482 490 if position == self._get_cursor().position():
483 491 self._call_tip()
@@ -1,552 +1,580 b''
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 import atexit
20 21 import sys
21 22 import time
22 23 import traceback
23 24
24 25 # System library imports.
25 26 import zmq
26 27
27 28 # Local imports.
28 29 from IPython.config.configurable import Configurable
29 30 from IPython.utils import io
30 31 from IPython.utils.jsonutil import json_clean
31 32 from IPython.lib import pylabtools
32 33 from IPython.utils.traitlets import Instance, Float
33 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
34 start_kernel
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 start_kernel)
35 36 from iostream import OutStream
36 37 from session import Session, Message
37 38 from zmqshell import ZMQInteractiveShell
38 39
39
40 40 #-----------------------------------------------------------------------------
41 41 # Main kernel class
42 42 #-----------------------------------------------------------------------------
43 43
44 44 class Kernel(Configurable):
45 45
46 46 #---------------------------------------------------------------------------
47 47 # Kernel interface
48 48 #---------------------------------------------------------------------------
49 49
50 50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
51 51 session = Instance(Session)
52 52 reply_socket = Instance('zmq.Socket')
53 53 pub_socket = Instance('zmq.Socket')
54 54 req_socket = Instance('zmq.Socket')
55 55
56 56 # Private interface
57 57
58 58 # Time to sleep after flushing the stdout/err buffers in each execute
59 59 # cycle. While this introduces a hard limit on the minimal latency of the
60 60 # execute cycle, it helps prevent output synchronization problems for
61 61 # clients.
62 62 # Units are in seconds. The minimum zmq latency on local host is probably
63 63 # ~150 microseconds, set this to 500us for now. We may need to increase it
64 64 # a little if it's not enough after more interactive testing.
65 65 _execute_sleep = Float(0.0005, config=True)
66 66
67 67 # Frequency of the kernel's event loop.
68 68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
69 69 # adapt to milliseconds.
70 70 _poll_interval = Float(0.05, config=True)
71
72 # If the shutdown was requested over the network, we leave here the
73 # necessary reply message so it can be sent by our registered atexit
74 # handler. This ensures that the reply is only sent to clients truly at
75 # the end of our shutdown process (which happens after the underlying
76 # IPython shell's own shutdown).
77 _shutdown_message = None
71 78
72 79 def __init__(self, **kwargs):
73 80 super(Kernel, self).__init__(**kwargs)
74 81
82 # Before we even start up the shell, register *first* our exit handlers
83 # so they come before the shell's
84 atexit.register(self._at_shutdown)
85
75 86 # Initialize the InteractiveShell subclass
76 87 self.shell = ZMQInteractiveShell.instance()
77 88 self.shell.displayhook.session = self.session
78 89 self.shell.displayhook.pub_socket = self.pub_socket
79 90
80 91 # TMP - hack while developing
81 92 self.shell._reply_content = None
82 93
83 94 # Build dict of handlers for message types
84 95 msg_types = [ 'execute_request', 'complete_request',
85 'object_info_request', 'history_request' ]
96 'object_info_request', 'history_request',
97 'shutdown_request']
86 98 self.handlers = {}
87 99 for msg_type in msg_types:
88 100 self.handlers[msg_type] = getattr(self, msg_type)
89 101
90 102 def do_one_iteration(self):
91 103 """Do one iteration of the kernel's evaluation loop.
92 104 """
93 105 try:
94 106 ident = self.reply_socket.recv(zmq.NOBLOCK)
95 107 except zmq.ZMQError, e:
96 108 if e.errno == zmq.EAGAIN:
97 109 return
98 110 else:
99 111 raise
100 112 # FIXME: Bug in pyzmq/zmq?
101 113 # assert self.reply_socket.rcvmore(), "Missing message part."
102 114 msg = self.reply_socket.recv_json()
103 115
104 116 # Print some info about this message and leave a '--->' marker, so it's
105 117 # easier to trace visually the message chain when debugging. Each
106 118 # handler prints its message at the end.
107 119 # Eventually we'll move these from stdout to a logger.
108 120 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
109 121 io.raw_print(' Content: ', msg['content'],
110 122 '\n --->\n ', sep='', end='')
111 123
112 124 # Find and call actual handler for message
113 125 handler = self.handlers.get(msg['msg_type'], None)
114 126 if handler is None:
115 127 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
116 128 else:
117 129 handler(ident, msg)
118 130
119 131 # Check whether we should exit, in case the incoming message set the
120 132 # exit flag on
121 133 if self.shell.exit_now:
122 134 io.raw_print('\nExiting IPython kernel...')
123 135 # We do a normal, clean exit, which allows any actions registered
124 136 # via atexit (such as history saving) to take place.
125 137 sys.exit(0)
126 138
127 139
128 140 def start(self):
129 141 """ Start the kernel main loop.
130 142 """
131 143 while True:
132 144 time.sleep(self._poll_interval)
133 145 self.do_one_iteration()
134 146
135 147 #---------------------------------------------------------------------------
136 148 # Kernel request handlers
137 149 #---------------------------------------------------------------------------
138 150
139 151 def _publish_pyin(self, code, parent):
140 152 """Publish the code request on the pyin stream."""
141 153
142 154 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
143 155 self.pub_socket.send_json(pyin_msg)
144 156
145 157 def execute_request(self, ident, parent):
146 158 try:
147 159 content = parent[u'content']
148 160 code = content[u'code']
149 161 silent = content[u'silent']
150 162 except:
151 163 io.raw_print_err("Got bad msg: ")
152 164 io.raw_print_err(Message(parent))
153 165 return
154 166
155 167 shell = self.shell # we'll need this a lot here
156 168
157 169 # Replace raw_input. Note that is not sufficient to replace
158 170 # raw_input in the user namespace.
159 171 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
160 172 __builtin__.raw_input = raw_input
161 173
162 174 # Set the parent message of the display hook and out streams.
163 175 shell.displayhook.set_parent(parent)
164 176 sys.stdout.set_parent(parent)
165 177 sys.stderr.set_parent(parent)
166 178
167 179 # Re-broadcast our input for the benefit of listening clients, and
168 180 # start computing output
169 181 if not silent:
170 182 self._publish_pyin(code, parent)
171 183
172 184 reply_content = {}
173 185 try:
174 186 if silent:
175 187 # runcode uses 'exec' mode, so no displayhook will fire, and it
176 188 # doesn't call logging or history manipulations. Print
177 189 # statements in that code will obviously still execute.
178 190 shell.runcode(code)
179 191 else:
180 192 # FIXME: runlines calls the exception handler itself.
181 193 shell._reply_content = None
182 194
183 195 # Experimental: cell mode! Test more before turning into
184 196 # default and removing the hacks around runlines.
185 197 shell.run_cell(code)
186 198 # For now leave this here until we're sure we can stop using it
187 199 #shell.runlines(code)
188 200 except:
189 201 status = u'error'
190 202 # FIXME: this code right now isn't being used yet by default,
191 203 # because the runlines() call above directly fires off exception
192 204 # reporting. This code, therefore, is only active in the scenario
193 205 # where runlines itself has an unhandled exception. We need to
194 206 # uniformize this, for all exception construction to come from a
195 207 # single location in the codbase.
196 208 etype, evalue, tb = sys.exc_info()
197 209 tb_list = traceback.format_exception(etype, evalue, tb)
198 210 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
199 211 else:
200 212 status = u'ok'
201 213 reply_content[u'payload'] = shell.payload_manager.read_payload()
202 214 # Be agressive about clearing the payload because we don't want
203 215 # it to sit in memory until the next execute_request comes in.
204 216 shell.payload_manager.clear_payload()
205 217
206 218 reply_content[u'status'] = status
207 219 # Compute the execution counter so clients can display prompts
208 220 reply_content['execution_count'] = shell.displayhook.prompt_count
209 221
210 222 # FIXME - fish exception info out of shell, possibly left there by
211 223 # runlines. We'll need to clean up this logic later.
212 224 if shell._reply_content is not None:
213 225 reply_content.update(shell._reply_content)
214 226
215 227 # At this point, we can tell whether the main code execution succeeded
216 228 # or not. If it did, we proceed to evaluate user_variables/expressions
217 229 if reply_content['status'] == 'ok':
218 230 reply_content[u'user_variables'] = \
219 231 shell.get_user_variables(content[u'user_variables'])
220 232 reply_content[u'user_expressions'] = \
221 233 shell.eval_expressions(content[u'user_expressions'])
222 234 else:
223 235 # If there was an error, don't even try to compute variables or
224 236 # expressions
225 237 reply_content[u'user_variables'] = {}
226 238 reply_content[u'user_expressions'] = {}
227 239
228 240 # Send the reply.
229 241 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
230 242 io.raw_print(reply_msg)
231 243
232 244 # Flush output before sending the reply.
233 245 sys.stdout.flush()
234 246 sys.stderr.flush()
235 247 # FIXME: on rare occasions, the flush doesn't seem to make it to the
236 248 # clients... This seems to mitigate the problem, but we definitely need
237 249 # to better understand what's going on.
238 250 if self._execute_sleep:
239 251 time.sleep(self._execute_sleep)
240 252
241 253 self.reply_socket.send(ident, zmq.SNDMORE)
242 254 self.reply_socket.send_json(reply_msg)
243 255 if reply_msg['content']['status'] == u'error':
244 256 self._abort_queue()
245 257
246 258 def complete_request(self, ident, parent):
247 259 txt, matches = self._complete(parent)
248 260 matches = {'matches' : matches,
249 261 'matched_text' : txt,
250 262 'status' : 'ok'}
251 263 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
252 264 matches, parent, ident)
253 265 io.raw_print(completion_msg)
254 266
255 267 def object_info_request(self, ident, parent):
256 268 object_info = self.shell.object_inspect(parent['content']['oname'])
257 269 # Before we send this object over, we turn it into a dict and we scrub
258 270 # it for JSON usage
259 271 oinfo = json_clean(object_info._asdict())
260 272 msg = self.session.send(self.reply_socket, 'object_info_reply',
261 273 oinfo, parent, ident)
262 274 io.raw_print(msg)
263 275
264 276 def history_request(self, ident, parent):
265 277 output = parent['content']['output']
266 278 index = parent['content']['index']
267 279 raw = parent['content']['raw']
268 280 hist = self.shell.get_history(index=index, raw=raw, output=output)
269 281 content = {'history' : hist}
270 282 msg = self.session.send(self.reply_socket, 'history_reply',
271 283 content, parent, ident)
272 284 io.raw_print(msg)
273 285
286 def shutdown_request(self, ident, parent):
287 self.shell.exit_now = True
288 self._shutdown_message = self.session.msg(u'shutdown_reply', {}, parent)
289 sys.exit(0)
290
274 291 #---------------------------------------------------------------------------
275 292 # Protected interface
276 293 #---------------------------------------------------------------------------
277 294
278 295 def _abort_queue(self):
279 296 while True:
280 297 try:
281 298 ident = self.reply_socket.recv(zmq.NOBLOCK)
282 299 except zmq.ZMQError, e:
283 300 if e.errno == zmq.EAGAIN:
284 301 break
285 302 else:
286 303 assert self.reply_socket.rcvmore(), \
287 304 "Unexpected missing message part."
288 305 msg = self.reply_socket.recv_json()
289 306 io.raw_print("Aborting:\n", Message(msg))
290 307 msg_type = msg['msg_type']
291 308 reply_type = msg_type.split('_')[0] + '_reply'
292 309 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
293 310 io.raw_print(reply_msg)
294 311 self.reply_socket.send(ident,zmq.SNDMORE)
295 312 self.reply_socket.send_json(reply_msg)
296 313 # We need to wait a bit for requests to come in. This can probably
297 314 # be set shorter for true asynchronous clients.
298 315 time.sleep(0.1)
299 316
300 317 def _raw_input(self, prompt, ident, parent):
301 318 # Flush output before making the request.
302 319 sys.stderr.flush()
303 320 sys.stdout.flush()
304 321
305 322 # Send the input request.
306 323 content = dict(prompt=prompt)
307 324 msg = self.session.msg(u'input_request', content, parent)
308 325 self.req_socket.send_json(msg)
309 326
310 327 # Await a response.
311 328 reply = self.req_socket.recv_json()
312 329 try:
313 330 value = reply['content']['value']
314 331 except:
315 332 io.raw_print_err("Got bad raw_input reply: ")
316 333 io.raw_print_err(Message(parent))
317 334 value = ''
318 335 return value
319 336
320 337 def _complete(self, msg):
321 338 c = msg['content']
322 339 try:
323 340 cpos = int(c['cursor_pos'])
324 341 except:
325 342 # If we don't get something that we can convert to an integer, at
326 343 # least attempt the completion guessing the cursor is at the end of
327 344 # the text, if there's any, and otherwise of the line
328 345 cpos = len(c['text'])
329 346 if cpos==0:
330 347 cpos = len(c['line'])
331 348 return self.shell.complete(c['text'], c['line'], cpos)
332 349
333 350 def _object_info(self, context):
334 351 symbol, leftover = self._symbol_from_context(context)
335 352 if symbol is not None and not leftover:
336 353 doc = getattr(symbol, '__doc__', '')
337 354 else:
338 355 doc = ''
339 356 object_info = dict(docstring = doc)
340 357 return object_info
341 358
342 359 def _symbol_from_context(self, context):
343 360 if not context:
344 361 return None, context
345 362
346 363 base_symbol_string = context[0]
347 364 symbol = self.shell.user_ns.get(base_symbol_string, None)
348 365 if symbol is None:
349 366 symbol = __builtin__.__dict__.get(base_symbol_string, None)
350 367 if symbol is None:
351 368 return None, context
352 369
353 370 context = context[1:]
354 371 for i, name in enumerate(context):
355 372 new_symbol = getattr(symbol, name, None)
356 373 if new_symbol is None:
357 374 return symbol, context[i:]
358 375 else:
359 376 symbol = new_symbol
360 377
361 378 return symbol, []
362 379
380 def _at_shutdown(self):
381 """Actions taken at shutdown by the kernel, called by python's atexit.
382 """
383 # io.rprint("Kernel at_shutdown") # dbg
384 if self._shutdown_message is not None:
385 self.reply_socket.send_json(self._shutdown_message)
386 io.raw_print(self._shutdown_message)
387 # A very short sleep to give zmq time to flush its message buffers
388 # before Python truly shuts down.
389 time.sleep(0.01)
390
363 391
364 392 class QtKernel(Kernel):
365 393 """A Kernel subclass with Qt support."""
366 394
367 395 def start(self):
368 396 """Start a kernel with QtPy4 event loop integration."""
369 397
370 from PyQt4 import QtGui, QtCore
371 from IPython.lib.guisupport import (
372 get_app_qt4, start_event_loop_qt4
373 )
398 from PyQt4 import QtCore
399 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
400
374 401 self.app = get_app_qt4([" "])
375 402 self.app.setQuitOnLastWindowClosed(False)
376 403 self.timer = QtCore.QTimer()
377 404 self.timer.timeout.connect(self.do_one_iteration)
378 405 # Units for the timer are in milliseconds
379 406 self.timer.start(1000*self._poll_interval)
380 407 start_event_loop_qt4(self.app)
381 408
382 409
383 410 class WxKernel(Kernel):
384 411 """A Kernel subclass with Wx support."""
385 412
386 413 def start(self):
387 414 """Start a kernel with wx event loop support."""
388 415
389 416 import wx
390 417 from IPython.lib.guisupport import start_event_loop_wx
418
391 419 doi = self.do_one_iteration
392 420 # Wx uses milliseconds
393 421 poll_interval = int(1000*self._poll_interval)
394 422
395 423 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
396 424 # We make the Frame hidden when we create it in the main app below.
397 425 class TimerFrame(wx.Frame):
398 426 def __init__(self, func):
399 427 wx.Frame.__init__(self, None, -1)
400 428 self.timer = wx.Timer(self)
401 429 # Units for the timer are in milliseconds
402 430 self.timer.Start(poll_interval)
403 431 self.Bind(wx.EVT_TIMER, self.on_timer)
404 432 self.func = func
405 433
406 434 def on_timer(self, event):
407 435 self.func()
408 436
409 437 # We need a custom wx.App to create our Frame subclass that has the
410 438 # wx.Timer to drive the ZMQ event loop.
411 439 class IPWxApp(wx.App):
412 440 def OnInit(self):
413 441 self.frame = TimerFrame(doi)
414 442 self.frame.Show(False)
415 443 return True
416 444
417 445 # The redirect=False here makes sure that wx doesn't replace
418 446 # sys.stdout/stderr with its own classes.
419 447 self.app = IPWxApp(redirect=False)
420 448 start_event_loop_wx(self.app)
421 449
422 450
423 451 class TkKernel(Kernel):
424 452 """A Kernel subclass with Tk support."""
425 453
426 454 def start(self):
427 455 """Start a Tk enabled event loop."""
428 456
429 457 import Tkinter
430 458 doi = self.do_one_iteration
431 459 # Tk uses milliseconds
432 460 poll_interval = int(1000*self._poll_interval)
433 461 # For Tkinter, we create a Tk object and call its withdraw method.
434 462 class Timer(object):
435 463 def __init__(self, func):
436 464 self.app = Tkinter.Tk()
437 465 self.app.withdraw()
438 466 self.func = func
439 467
440 468 def on_timer(self):
441 469 self.func()
442 470 self.app.after(poll_interval, self.on_timer)
443 471
444 472 def start(self):
445 473 self.on_timer() # Call it once to get things going.
446 474 self.app.mainloop()
447 475
448 476 self.timer = Timer(doi)
449 477 self.timer.start()
450 478
451 479
452 480 class GTKKernel(Kernel):
453 481 """A Kernel subclass with GTK support."""
454 482
455 483 def start(self):
456 484 """Start the kernel, coordinating with the GTK event loop"""
457 485 from .gui.gtkembed import GTKEmbed
458 486
459 487 gtk_kernel = GTKEmbed(self)
460 488 gtk_kernel.start()
461 489
462 490
463 491 #-----------------------------------------------------------------------------
464 492 # Kernel main and launch functions
465 493 #-----------------------------------------------------------------------------
466 494
467 495 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
468 496 independent=False, pylab=False):
469 497 """Launches a localhost kernel, binding to the specified ports.
470 498
471 499 Parameters
472 500 ----------
473 501 xrep_port : int, optional
474 502 The port to use for XREP channel.
475 503
476 504 pub_port : int, optional
477 505 The port to use for the SUB channel.
478 506
479 507 req_port : int, optional
480 508 The port to use for the REQ (raw input) channel.
481 509
482 510 hb_port : int, optional
483 511 The port to use for the hearbeat REP channel.
484 512
485 513 independent : bool, optional (default False)
486 514 If set, the kernel process is guaranteed to survive if this process
487 515 dies. If not set, an effort is made to ensure that the kernel is killed
488 516 when this process dies. Note that in this case it is still good practice
489 517 to kill kernels manually before exiting.
490 518
491 519 pylab : bool or string, optional (default False)
492 520 If not False, the kernel will be launched with pylab enabled. If a
493 521 string is passed, matplotlib will use the specified backend. Otherwise,
494 522 matplotlib's default backend will be used.
495 523
496 524 Returns
497 525 -------
498 526 A tuple of form:
499 527 (kernel_process, xrep_port, pub_port, req_port)
500 528 where kernel_process is a Popen object and the ports are integers.
501 529 """
502 530 extra_arguments = []
503 531 if pylab:
504 532 extra_arguments.append('--pylab')
505 533 if isinstance(pylab, basestring):
506 534 extra_arguments.append(pylab)
507 535 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
508 536 xrep_port, pub_port, req_port, hb_port,
509 537 independent, extra_arguments)
510 538
511 539
512 540 def main():
513 541 """ The IPython kernel main entry point.
514 542 """
515 543 parser = make_argument_parser()
516 544 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
517 545 const='auto', help = \
518 546 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
519 547 given, the GUI backend is matplotlib's, otherwise use one of: \
520 548 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
521 549 namespace = parser.parse_args()
522 550
523 551 kernel_class = Kernel
524 552
525 553 kernel_classes = {
526 554 'qt' : QtKernel,
527 555 'qt4': QtKernel,
528 556 'payload-svg': Kernel,
529 557 'wx' : WxKernel,
530 558 'tk' : TkKernel,
531 559 'gtk': GTKKernel,
532 560 }
533 561 if namespace.pylab:
534 562 if namespace.pylab == 'auto':
535 563 gui, backend = pylabtools.find_gui_and_backend()
536 564 else:
537 565 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
538 566 kernel_class = kernel_classes.get(gui)
539 567 if kernel_class is None:
540 568 raise ValueError('GUI is not supported: %r' % gui)
541 569 pylabtools.activate_matplotlib(backend)
542 570
543 571 kernel = make_kernel(namespace, kernel_class, OutStream)
544 572
545 573 if namespace.pylab:
546 574 pylabtools.import_pylab(kernel.shell.user_ns)
547 575
548 576 start_kernel(namespace, kernel)
549 577
550 578
551 579 if __name__ == '__main__':
552 580 main()
@@ -1,806 +1,835 b''
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 Todo
4 4 ====
5 5
6 6 * Create logger to handle debugging and console messages.
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2010 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 # Standard library imports.
21 21 from Queue import Queue, Empty
22 22 from subprocess import Popen
23 23 from threading import Thread
24 24 import time
25 25
26 26 # System library imports.
27 27 import zmq
28 28 from zmq import POLLIN, POLLOUT, POLLERR
29 29 from zmq.eventloop import ioloop
30 30
31 31 # Local imports.
32 32 from IPython.utils import io
33 33 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
34 34 from session import Session
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Constants and exceptions
38 38 #-----------------------------------------------------------------------------
39 39
40 40 LOCALHOST = '127.0.0.1'
41 41
42 42 class InvalidPortNumber(Exception):
43 43 pass
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Utility functions
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # some utilities to validate message structure, these might get moved elsewhere
50 50 # if they prove to have more generic utility
51 51
52 52 def validate_string_list(lst):
53 53 """Validate that the input is a list of strings.
54 54
55 55 Raises ValueError if not."""
56 56 if not isinstance(lst, list):
57 57 raise ValueError('input %r must be a list' % lst)
58 58 for x in lst:
59 59 if not isinstance(x, basestring):
60 60 raise ValueError('element %r in list must be a string' % x)
61 61
62 62
63 63 def validate_string_dict(dct):
64 64 """Validate that the input is a dict with string keys and values.
65 65
66 66 Raises ValueError if not."""
67 67 for k,v in dct.iteritems():
68 68 if not isinstance(k, basestring):
69 69 raise ValueError('key %r in dict must be a string' % k)
70 70 if not isinstance(v, basestring):
71 71 raise ValueError('value %r in dict must be a string' % v)
72 72
73 73
74 74 #-----------------------------------------------------------------------------
75 75 # ZMQ Socket Channel classes
76 76 #-----------------------------------------------------------------------------
77 77
78 78 class ZmqSocketChannel(Thread):
79 79 """The base class for the channels that use ZMQ sockets.
80 80 """
81 81 context = None
82 82 session = None
83 83 socket = None
84 84 ioloop = None
85 85 iostate = None
86 86 _address = None
87 87
88 88 def __init__(self, context, session, address):
89 89 """Create a channel
90 90
91 91 Parameters
92 92 ----------
93 93 context : :class:`zmq.Context`
94 94 The ZMQ context to use.
95 95 session : :class:`session.Session`
96 96 The session to use.
97 97 address : tuple
98 98 Standard (ip, port) tuple that the kernel is listening on.
99 99 """
100 100 super(ZmqSocketChannel, self).__init__()
101 101 self.daemon = True
102 102
103 103 self.context = context
104 104 self.session = session
105 105 if address[1] == 0:
106 106 message = 'The port number for a channel cannot be 0.'
107 107 raise InvalidPortNumber(message)
108 108 self._address = address
109 109
110 110 def stop(self):
111 111 """Stop the channel's activity.
112 112
113 113 This calls :method:`Thread.join` and returns when the thread
114 114 terminates. :class:`RuntimeError` will be raised if
115 115 :method:`self.start` is called again.
116 116 """
117 117 self.join()
118 118
119 119 @property
120 120 def address(self):
121 121 """Get the channel's address as an (ip, port) tuple.
122 122
123 123 By the default, the address is (localhost, 0), where 0 means a random
124 124 port.
125 125 """
126 126 return self._address
127 127
128 128 def add_io_state(self, state):
129 129 """Add IO state to the eventloop.
130 130
131 131 Parameters
132 132 ----------
133 133 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
134 134 The IO state flag to set.
135 135
136 136 This is thread safe as it uses the thread safe IOLoop.add_callback.
137 137 """
138 138 def add_io_state_callback():
139 139 if not self.iostate & state:
140 140 self.iostate = self.iostate | state
141 141 self.ioloop.update_handler(self.socket, self.iostate)
142 142 self.ioloop.add_callback(add_io_state_callback)
143 143
144 144 def drop_io_state(self, state):
145 145 """Drop IO state from the eventloop.
146 146
147 147 Parameters
148 148 ----------
149 149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 150 The IO state flag to set.
151 151
152 152 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 153 """
154 154 def drop_io_state_callback():
155 155 if self.iostate & state:
156 156 self.iostate = self.iostate & (~state)
157 157 self.ioloop.update_handler(self.socket, self.iostate)
158 158 self.ioloop.add_callback(drop_io_state_callback)
159 159
160 160
161 161 class XReqSocketChannel(ZmqSocketChannel):
162 162 """The XREQ channel for issues request/replies to the kernel.
163 163 """
164 164
165 165 command_queue = None
166 166
167 167 def __init__(self, context, session, address):
168 168 self.command_queue = Queue()
169 169 super(XReqSocketChannel, self).__init__(context, session, address)
170 170
171 171 def run(self):
172 172 """The thread's main activity. Call start() instead."""
173 173 self.socket = self.context.socket(zmq.XREQ)
174 174 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
175 175 self.socket.connect('tcp://%s:%i' % self.address)
176 176 self.ioloop = ioloop.IOLoop()
177 177 self.iostate = POLLERR|POLLIN
178 178 self.ioloop.add_handler(self.socket, self._handle_events,
179 179 self.iostate)
180 180 self.ioloop.start()
181 181
182 182 def stop(self):
183 183 self.ioloop.stop()
184 184 super(XReqSocketChannel, self).stop()
185 185
186 186 def call_handlers(self, msg):
187 187 """This method is called in the ioloop thread when a message arrives.
188 188
189 189 Subclasses should override this method to handle incoming messages.
190 190 It is important to remember that this method is called in the thread
191 191 so that some logic must be done to ensure that the application leve
192 192 handlers are called in the application thread.
193 193 """
194 194 raise NotImplementedError('call_handlers must be defined in a subclass.')
195 195
196 196 def execute(self, code, silent=False,
197 197 user_variables=None, user_expressions=None):
198 198 """Execute code in the kernel.
199 199
200 200 Parameters
201 201 ----------
202 202 code : str
203 203 A string of Python code.
204 204
205 205 silent : bool, optional (default False)
206 206 If set, the kernel will execute the code as quietly possible.
207 207
208 208 user_variables : list, optional
209 209 A list of variable names to pull from the user's namespace. They
210 210 will come back as a dict with these names as keys and their
211 211 :func:`repr` as values.
212 212
213 213 user_expressions : dict, optional
214 214 A dict with string keys and to pull from the user's
215 215 namespace. They will come back as a dict with these names as keys
216 216 and their :func:`repr` as values.
217 217
218 218 Returns
219 219 -------
220 220 The msg_id of the message sent.
221 221 """
222 222 if user_variables is None:
223 223 user_variables = []
224 224 if user_expressions is None:
225 225 user_expressions = {}
226 226
227 227 # Don't waste network traffic if inputs are invalid
228 228 if not isinstance(code, basestring):
229 229 raise ValueError('code %r must be a string' % code)
230 230 validate_string_list(user_variables)
231 231 validate_string_dict(user_expressions)
232 232
233 233 # Create class for content/msg creation. Related to, but possibly
234 234 # not in Session.
235 235 content = dict(code=code, silent=silent,
236 236 user_variables=user_variables,
237 237 user_expressions=user_expressions)
238 238 msg = self.session.msg('execute_request', content)
239 239 self._queue_request(msg)
240 240 return msg['header']['msg_id']
241 241
242 242 def complete(self, text, line, cursor_pos, block=None):
243 243 """Tab complete text in the kernel's namespace.
244 244
245 245 Parameters
246 246 ----------
247 247 text : str
248 248 The text to complete.
249 249 line : str
250 250 The full line of text that is the surrounding context for the
251 251 text to complete.
252 252 cursor_pos : int
253 253 The position of the cursor in the line where the completion was
254 254 requested.
255 255 block : str, optional
256 256 The full block of code in which the completion is being requested.
257 257
258 258 Returns
259 259 -------
260 260 The msg_id of the message sent.
261 261 """
262 262 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
263 263 msg = self.session.msg('complete_request', content)
264 264 self._queue_request(msg)
265 265 return msg['header']['msg_id']
266 266
267 267 def object_info(self, oname):
268 268 """Get metadata information about an object.
269 269
270 270 Parameters
271 271 ----------
272 272 oname : str
273 273 A string specifying the object name.
274 274
275 275 Returns
276 276 -------
277 277 The msg_id of the message sent.
278 278 """
279 279 content = dict(oname=oname)
280 280 msg = self.session.msg('object_info_request', content)
281 281 self._queue_request(msg)
282 282 return msg['header']['msg_id']
283 283
284 284 def history(self, index=None, raw=False, output=True):
285 285 """Get the history list.
286 286
287 287 Parameters
288 288 ----------
289 289 index : n or (n1, n2) or None
290 290 If n, then the last entries. If a tuple, then all in
291 291 range(n1, n2). If None, then all entries. Raises IndexError if
292 292 the format of index is incorrect.
293 293 raw : bool
294 294 If True, return the raw input.
295 295 output : bool
296 296 If True, then return the output as well.
297 297
298 298 Returns
299 299 -------
300 300 The msg_id of the message sent.
301 301 """
302 302 content = dict(index=index, raw=raw, output=output)
303 303 msg = self.session.msg('history_request', content)
304 304 self._queue_request(msg)
305 305 return msg['header']['msg_id']
306 306
307 def shutdown(self):
308 """Request an immediate kernel shutdown.
309
310 Upon receipt of the (empty) reply, client code can safely assume that
311 the kernel has shut down and it's safe to forcefully terminate it if
312 it's still alive.
313
314 The kernel will send the reply via a function registered with Python's
315 atexit module, ensuring it's truly done as the kernel is done with all
316 normal operation.
317 """
318 # Send quit message to kernel. Once we implement kernel-side setattr,
319 # this should probably be done that way, but for now this will do.
320 msg = self.session.msg('shutdown_request', {})
321 self._queue_request(msg)
322 return msg['header']['msg_id']
323
307 324 def _handle_events(self, socket, events):
308 325 if events & POLLERR:
309 326 self._handle_err()
310 327 if events & POLLOUT:
311 328 self._handle_send()
312 329 if events & POLLIN:
313 330 self._handle_recv()
314 331
315 332 def _handle_recv(self):
316 333 msg = self.socket.recv_json()
317 334 self.call_handlers(msg)
318 335
319 336 def _handle_send(self):
320 337 try:
321 338 msg = self.command_queue.get(False)
322 339 except Empty:
323 340 pass
324 341 else:
325 342 self.socket.send_json(msg)
326 343 if self.command_queue.empty():
327 344 self.drop_io_state(POLLOUT)
328 345
329 346 def _handle_err(self):
330 347 # We don't want to let this go silently, so eventually we should log.
331 348 raise zmq.ZMQError()
332 349
333 350 def _queue_request(self, msg):
334 351 self.command_queue.put(msg)
335 352 self.add_io_state(POLLOUT)
336 353
337 354
338 355 class SubSocketChannel(ZmqSocketChannel):
339 356 """The SUB channel which listens for messages that the kernel publishes.
340 357 """
341 358
342 359 def __init__(self, context, session, address):
343 360 super(SubSocketChannel, self).__init__(context, session, address)
344 361
345 362 def run(self):
346 363 """The thread's main activity. Call start() instead."""
347 364 self.socket = self.context.socket(zmq.SUB)
348 365 self.socket.setsockopt(zmq.SUBSCRIBE,'')
349 366 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
350 367 self.socket.connect('tcp://%s:%i' % self.address)
351 368 self.ioloop = ioloop.IOLoop()
352 369 self.iostate = POLLIN|POLLERR
353 370 self.ioloop.add_handler(self.socket, self._handle_events,
354 371 self.iostate)
355 372 self.ioloop.start()
356 373
357 374 def stop(self):
358 375 self.ioloop.stop()
359 376 super(SubSocketChannel, self).stop()
360 377
361 378 def call_handlers(self, msg):
362 379 """This method is called in the ioloop thread when a message arrives.
363 380
364 381 Subclasses should override this method to handle incoming messages.
365 382 It is important to remember that this method is called in the thread
366 383 so that some logic must be done to ensure that the application leve
367 384 handlers are called in the application thread.
368 385 """
369 386 raise NotImplementedError('call_handlers must be defined in a subclass.')
370 387
371 388 def flush(self, timeout=1.0):
372 389 """Immediately processes all pending messages on the SUB channel.
373 390
374 391 Callers should use this method to ensure that :method:`call_handlers`
375 392 has been called for all messages that have been received on the
376 393 0MQ SUB socket of this channel.
377 394
378 395 This method is thread safe.
379 396
380 397 Parameters
381 398 ----------
382 399 timeout : float, optional
383 400 The maximum amount of time to spend flushing, in seconds. The
384 401 default is one second.
385 402 """
386 403 # We do the IOLoop callback process twice to ensure that the IOLoop
387 404 # gets to perform at least one full poll.
388 405 stop_time = time.time() + timeout
389 406 for i in xrange(2):
390 407 self._flushed = False
391 408 self.ioloop.add_callback(self._flush)
392 409 while not self._flushed and time.time() < stop_time:
393 410 time.sleep(0.01)
394 411
395 412 def _handle_events(self, socket, events):
396 413 # Turn on and off POLLOUT depending on if we have made a request
397 414 if events & POLLERR:
398 415 self._handle_err()
399 416 if events & POLLIN:
400 417 self._handle_recv()
401 418
402 419 def _handle_err(self):
403 420 # We don't want to let this go silently, so eventually we should log.
404 421 raise zmq.ZMQError()
405 422
406 423 def _handle_recv(self):
407 424 # Get all of the messages we can
408 425 while True:
409 426 try:
410 427 msg = self.socket.recv_json(zmq.NOBLOCK)
411 428 except zmq.ZMQError:
412 429 # Check the errno?
413 430 # Will this trigger POLLERR?
414 431 break
415 432 else:
416 433 self.call_handlers(msg)
417 434
418 435 def _flush(self):
419 436 """Callback for :method:`self.flush`."""
420 437 self._flushed = True
421 438
422 439
423 440 class RepSocketChannel(ZmqSocketChannel):
424 441 """A reply channel to handle raw_input requests that the kernel makes."""
425 442
426 443 msg_queue = None
427 444
428 445 def __init__(self, context, session, address):
429 446 self.msg_queue = Queue()
430 447 super(RepSocketChannel, self).__init__(context, session, address)
431 448
432 449 def run(self):
433 450 """The thread's main activity. Call start() instead."""
434 451 self.socket = self.context.socket(zmq.XREQ)
435 452 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
436 453 self.socket.connect('tcp://%s:%i' % self.address)
437 454 self.ioloop = ioloop.IOLoop()
438 455 self.iostate = POLLERR|POLLIN
439 456 self.ioloop.add_handler(self.socket, self._handle_events,
440 457 self.iostate)
441 458 self.ioloop.start()
442 459
443 460 def stop(self):
444 461 self.ioloop.stop()
445 462 super(RepSocketChannel, self).stop()
446 463
447 464 def call_handlers(self, msg):
448 465 """This method is called in the ioloop thread when a message arrives.
449 466
450 467 Subclasses should override this method to handle incoming messages.
451 468 It is important to remember that this method is called in the thread
452 469 so that some logic must be done to ensure that the application leve
453 470 handlers are called in the application thread.
454 471 """
455 472 raise NotImplementedError('call_handlers must be defined in a subclass.')
456 473
457 474 def input(self, string):
458 475 """Send a string of raw input to the kernel."""
459 476 content = dict(value=string)
460 477 msg = self.session.msg('input_reply', content)
461 478 self._queue_reply(msg)
462 479
463 480 def _handle_events(self, socket, events):
464 481 if events & POLLERR:
465 482 self._handle_err()
466 483 if events & POLLOUT:
467 484 self._handle_send()
468 485 if events & POLLIN:
469 486 self._handle_recv()
470 487
471 488 def _handle_recv(self):
472 489 msg = self.socket.recv_json()
473 490 self.call_handlers(msg)
474 491
475 492 def _handle_send(self):
476 493 try:
477 494 msg = self.msg_queue.get(False)
478 495 except Empty:
479 496 pass
480 497 else:
481 498 self.socket.send_json(msg)
482 499 if self.msg_queue.empty():
483 500 self.drop_io_state(POLLOUT)
484 501
485 502 def _handle_err(self):
486 503 # We don't want to let this go silently, so eventually we should log.
487 504 raise zmq.ZMQError()
488 505
489 506 def _queue_reply(self, msg):
490 507 self.msg_queue.put(msg)
491 508 self.add_io_state(POLLOUT)
492 509
493 510
494 511 class HBSocketChannel(ZmqSocketChannel):
495 512 """The heartbeat channel which monitors the kernel heartbeat."""
496 513
497 514 time_to_dead = 3.0
498 515 socket = None
499 516 poller = None
500 517
501 518 def __init__(self, context, session, address):
502 519 super(HBSocketChannel, self).__init__(context, session, address)
503 520 self._running = False
504 521
505 522 def _create_socket(self):
506 523 self.socket = self.context.socket(zmq.REQ)
507 524 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
508 525 self.socket.connect('tcp://%s:%i' % self.address)
509 526 self.poller = zmq.Poller()
510 527 self.poller.register(self.socket, zmq.POLLIN)
511 528
512 529 def run(self):
513 530 """The thread's main activity. Call start() instead."""
514 531 self._create_socket()
515 532 self._running = True
516 533 # Wait 2 seconds for the kernel to come up and the sockets to auto
517 534 # connect. If we don't we will see the kernel as dead. Also, before
518 535 # the sockets are connected, the poller.poll line below is returning
519 536 # too fast. This avoids that because the polling doesn't start until
520 537 # after the sockets are connected.
521 538 time.sleep(2.0)
522 539 while self._running:
523 540 since_last_heartbeat = 0.0
524 541 request_time = time.time()
525 542 try:
526 543 #io.rprint('Ping from HB channel') # dbg
527 544 self.socket.send_json('ping')
528 545 except zmq.ZMQError, e:
529 546 #io.rprint('*** HB Error:', e) # dbg
530 547 if e.errno == zmq.EFSM:
531 548 #io.rprint('sleep...', self.time_to_dead) # dbg
532 549 time.sleep(self.time_to_dead)
533 550 self._create_socket()
534 551 else:
535 552 raise
536 553 else:
537 554 while True:
538 555 try:
539 556 self.socket.recv_json(zmq.NOBLOCK)
540 557 except zmq.ZMQError, e:
541 558 #io.rprint('*** HB Error 2:', e) # dbg
542 559 if e.errno == zmq.EAGAIN:
543 560 before_poll = time.time()
544 561 until_dead = self.time_to_dead - (before_poll -
545 562 request_time)
546 563
547 564 # When the return value of poll() is an empty list,
548 565 # that is when things have gone wrong (zeromq bug).
549 566 # As long as it is not an empty list, poll is
550 567 # working correctly even if it returns quickly.
551 568 # Note: poll timeout is in milliseconds.
552 569 self.poller.poll(1000*until_dead)
553 570
554 571 since_last_heartbeat = time.time() - request_time
555 572 if since_last_heartbeat > self.time_to_dead:
556 573 self.call_handlers(since_last_heartbeat)
557 574 break
558 575 else:
559 576 # FIXME: We should probably log this instead.
560 577 raise
561 578 else:
562 579 until_dead = self.time_to_dead - (time.time() -
563 580 request_time)
564 581 if until_dead > 0.0:
565 582 #io.rprint('sleep...', self.time_to_dead) # dbg
566 583 time.sleep(until_dead)
567 584 break
568 585
569 586 def stop(self):
570 587 self._running = False
571 588 super(HBSocketChannel, self).stop()
572 589
573 590 def call_handlers(self, since_last_heartbeat):
574 591 """This method is called in the ioloop thread when a message arrives.
575 592
576 593 Subclasses should override this method to handle incoming messages.
577 594 It is important to remember that this method is called in the thread
578 595 so that some logic must be done to ensure that the application leve
579 596 handlers are called in the application thread.
580 597 """
581 598 raise NotImplementedError('call_handlers must be defined in a subclass.')
582 599
583 600
584 601 #-----------------------------------------------------------------------------
585 602 # Main kernel manager class
586 603 #-----------------------------------------------------------------------------
587 604
588 605 class KernelManager(HasTraits):
589 606 """ Manages a kernel for a frontend.
590 607
591 608 The SUB channel is for the frontend to receive messages published by the
592 609 kernel.
593 610
594 611 The REQ channel is for the frontend to make requests of the kernel.
595 612
596 613 The REP channel is for the kernel to request stdin (raw_input) from the
597 614 frontend.
598 615 """
599 616 # The PyZMQ Context to use for communication with the kernel.
600 617 context = Instance(zmq.Context,(),{})
601 618
602 619 # The Session to use for communication with the kernel.
603 620 session = Instance(Session,(),{})
604 621
605 622 # The kernel process with which the KernelManager is communicating.
606 623 kernel = Instance(Popen)
607 624
608 625 # The addresses for the communication channels.
609 626 xreq_address = TCPAddress((LOCALHOST, 0))
610 627 sub_address = TCPAddress((LOCALHOST, 0))
611 628 rep_address = TCPAddress((LOCALHOST, 0))
612 629 hb_address = TCPAddress((LOCALHOST, 0))
613 630
614 631 # The classes to use for the various channels.
615 632 xreq_channel_class = Type(XReqSocketChannel)
616 633 sub_channel_class = Type(SubSocketChannel)
617 634 rep_channel_class = Type(RepSocketChannel)
618 635 hb_channel_class = Type(HBSocketChannel)
619 636
620 637 # Protected traits.
621 638 _launch_args = Any
622 639 _xreq_channel = Any
623 640 _sub_channel = Any
624 641 _rep_channel = Any
625 642 _hb_channel = Any
626 643
627 644 #--------------------------------------------------------------------------
628 645 # Channel management methods:
629 646 #--------------------------------------------------------------------------
630 647
631 648 def start_channels(self):
632 649 """Starts the channels for this kernel.
633 650
634 651 This will create the channels if they do not exist and then start
635 652 them. If port numbers of 0 are being used (random ports) then you
636 653 must first call :method:`start_kernel`. If the channels have been
637 654 stopped and you call this, :class:`RuntimeError` will be raised.
638 655 """
639 656 self.xreq_channel.start()
640 657 self.sub_channel.start()
641 658 self.rep_channel.start()
642 659 self.hb_channel.start()
643 660
644 661 def stop_channels(self):
645 662 """Stops the channels for this kernel.
646 663
647 664 This stops the channels by joining their threads. If the channels
648 665 were not started, :class:`RuntimeError` will be raised.
649 666 """
650 667 self.xreq_channel.stop()
651 668 self.sub_channel.stop()
652 669 self.rep_channel.stop()
653 670 self.hb_channel.stop()
654 671
655 672 @property
656 673 def channels_running(self):
657 674 """Are all of the channels created and running?"""
658 675 return self.xreq_channel.is_alive() \
659 676 and self.sub_channel.is_alive() \
660 677 and self.rep_channel.is_alive() \
661 678 and self.hb_channel.is_alive()
662 679
663 680 #--------------------------------------------------------------------------
664 681 # Kernel process management methods:
665 682 #--------------------------------------------------------------------------
666 683
667 684 def start_kernel(self, **kw):
668 685 """Starts a kernel process and configures the manager to use it.
669 686
670 687 If random ports (port=0) are being used, this method must be called
671 688 before the channels are created.
672 689
673 690 Parameters:
674 691 -----------
675 692 ipython : bool, optional (default True)
676 693 Whether to use an IPython kernel instead of a plain Python kernel.
677 694 """
678 695 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
679 696 self.rep_address, self.hb_address
680 697 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
681 698 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
682 699 raise RuntimeError("Can only launch a kernel on localhost."
683 700 "Make sure that the '*_address' attributes are "
684 701 "configured properly.")
685 702
686 703 self._launch_args = kw.copy()
687 704 if kw.pop('ipython', True):
688 705 from ipkernel import launch_kernel
689 706 else:
690 707 from pykernel import launch_kernel
691 708 self.kernel, xrep, pub, req, hb = launch_kernel(
692 709 xrep_port=xreq[1], pub_port=sub[1],
693 710 req_port=rep[1], hb_port=hb[1], **kw)
694 711 self.xreq_address = (LOCALHOST, xrep)
695 712 self.sub_address = (LOCALHOST, pub)
696 713 self.rep_address = (LOCALHOST, req)
697 714 self.hb_address = (LOCALHOST, hb)
698 715
699 716 def shutdown_kernel(self):
700 717 """ Attempts to the stop the kernel process cleanly. If the kernel
701 718 cannot be stopped, it is killed, if possible.
702 719 """
703 # Send quit message to kernel. Once we implement kernel-side setattr,
704 # this should probably be done that way, but for now this will do.
705 self.xreq_channel.execute('get_ipython().exit_now=True', silent=True)
706
720 self.xreq_channel.shutdown()
707 721 # Don't send any additional kernel kill messages immediately, to give
708 722 # the kernel a chance to properly execute shutdown actions. Wait for at
709 # most 2s, checking every 0.1s.
710 for i in range(20):
723 # most 1s, checking every 0.1s.
724 for i in range(10):
711 725 if self.is_alive:
712 726 time.sleep(0.1)
713 727 else:
714 728 break
715 729 else:
716 730 # OK, we've waited long enough.
717 731 if self.has_kernel:
718 732 self.kill_kernel()
719
720 def restart_kernel(self):
733
734 def restart_kernel(self, instant_death=False):
721 735 """Restarts a kernel with the same arguments that were used to launch
722 736 it. If the old kernel was launched with random ports, the same ports
723 737 will be used for the new kernel.
738
739 Parameters
740 ----------
741 instant_death : bool, optional
742 If True, the kernel is forcefully restarted *immediately*, without
743 having a chance to do any cleanup action. Otherwise the kernel is
744 given 1s to clean up before a forceful restart is issued.
745
746 In all cases the kernel is restarted, the only difference is whether
747 it is given a chance to perform a clean shutdown or not.
724 748 """
725 749 if self._launch_args is None:
726 750 raise RuntimeError("Cannot restart the kernel. "
727 751 "No previous call to 'start_kernel'.")
728 752 else:
729 753 if self.has_kernel:
730 self.kill_kernel()
754 if instant_death:
755 self.kill_kernel()
756 else:
757 self.shutdown_kernel()
731 758 self.start_kernel(**self._launch_args)
732 759
733 760 @property
734 761 def has_kernel(self):
735 762 """Returns whether a kernel process has been specified for the kernel
736 763 manager.
737 764 """
738 765 return self.kernel is not None
739 766
740 767 def kill_kernel(self):
741 768 """ Kill the running kernel. """
742 769 if self.kernel is not None:
743 770 self.kernel.kill()
744 771 self.kernel = None
745 772 else:
746 773 raise RuntimeError("Cannot kill kernel. No kernel is running!")
747 774
748 775 def signal_kernel(self, signum):
749 776 """ Sends a signal to the kernel. """
750 777 if self.kernel is not None:
751 778 self.kernel.send_signal(signum)
752 779 else:
753 780 raise RuntimeError("Cannot signal kernel. No kernel is running!")
754 781
755 782 @property
756 783 def is_alive(self):
757 784 """Is the kernel process still running?"""
785 # FIXME: not using a heartbeat means this method is broken for any
786 # remote kernel, it's only capable of handling local kernels.
758 787 if self.kernel is not None:
759 788 if self.kernel.poll() is None:
760 789 return True
761 790 else:
762 791 return False
763 792 else:
764 793 # We didn't start the kernel with this KernelManager so we don't
765 794 # know if it is running. We should use a heartbeat for this case.
766 795 return True
767 796
768 797 #--------------------------------------------------------------------------
769 798 # Channels used for communication with the kernel:
770 799 #--------------------------------------------------------------------------
771 800
772 801 @property
773 802 def xreq_channel(self):
774 803 """Get the REQ socket channel object to make requests of the kernel."""
775 804 if self._xreq_channel is None:
776 805 self._xreq_channel = self.xreq_channel_class(self.context,
777 806 self.session,
778 807 self.xreq_address)
779 808 return self._xreq_channel
780 809
781 810 @property
782 811 def sub_channel(self):
783 812 """Get the SUB socket channel object."""
784 813 if self._sub_channel is None:
785 814 self._sub_channel = self.sub_channel_class(self.context,
786 815 self.session,
787 816 self.sub_address)
788 817 return self._sub_channel
789 818
790 819 @property
791 820 def rep_channel(self):
792 821 """Get the REP socket channel object to handle stdin (raw_input)."""
793 822 if self._rep_channel is None:
794 823 self._rep_channel = self.rep_channel_class(self.context,
795 824 self.session,
796 825 self.rep_address)
797 826 return self._rep_channel
798 827
799 828 @property
800 829 def hb_channel(self):
801 830 """Get the REP socket channel object to handle stdin (raw_input)."""
802 831 if self._hb_channel is None:
803 832 self._hb_channel = self.hb_channel_class(self.context,
804 833 self.session,
805 834 self.hb_address)
806 835 return self._hb_channel
@@ -1,716 +1,758 b''
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, but this may be a multiline
164 164 string. The kernel is responsible for splitting this into possibly more than
165 165 one block and deciding whether to compile these in 'single' or 'exec' mode.
166 166 We're still sorting out this policy. The current inputsplitter is capable of
167 167 splitting the input for blocks that can all be run as 'single', but in the long
168 168 run it may prove cleaner to only use 'single' mode for truly single-line
169 169 inputs, and run all multiline input in 'exec' mode. This would preserve the
170 170 natural behavior of single-line inputs while allowing long cells to behave more
171 171 likea a script. This design will be refined as we complete the implementation.
172 172
173 173 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
174 174 the notion of a prompt string that allowed arbitrary code to be evaluated, and
175 175 this was put to good use by many in creating prompts that displayed system
176 176 status, path information, and even more esoteric uses like remote instrument
177 177 status aqcuired over the network. But now that IPython has a clean separation
178 178 between the kernel and the clients, the notion of embedding 'prompt'
179 179 maninpulations into the kernel itself feels awkward. Prompts should be a
180 180 frontend-side feature, and it should be even possible for different frontends
181 181 to display different prompts while interacting with the same kernel.
182 182
183 183 We have therefore abandoned the idea of a 'prompt string' to be evaluated by
184 184 the kernel, and instead provide the ability to retrieve from the user's
185 185 namespace information after the execution of the main ``code``, with two fields
186 186 of the execution request:
187 187
188 188 - ``user_variables``: If only variables from the user's namespace are needed, a
189 189 list of variable names can be passed and a dict with these names as keys and
190 190 their :func:`repr()` as values will be returned.
191 191
192 192 - ``user_expressions``: For more complex expressions that require function
193 193 evaluations, a dict can be provided with string keys and arbitrary python
194 194 expressions as values. The return message will contain also a dict with the
195 195 same keys and the :func:`repr()` of the evaluated expressions as value.
196 196
197 197 With this information, frontends can display any status information they wish
198 198 in the form that best suits each frontend (a status line, a popup, inline for a
199 199 terminal, etc).
200 200
201 201 .. Note::
202 202
203 203 In order to obtain the current execution counter for the purposes of
204 204 displaying input prompts, frontends simply make an execution request with an
205 205 empty code string and ``silent=True``.
206 206
207 207 Execution semantics
208 208 Upon completion of the execution request, the kernel *always* sends a
209 209 reply, with a status code indicating what happened and additional data
210 210 depending on the outcome.
211 211
212 212 The ``code`` field is executed first, and then the ``user_variables`` and
213 213 ``user_expressions`` are computed. This ensures that any error in the
214 214 latter don't harm the main code execution.
215 215
216 216 Any error in retrieving the ``user_variables`` or evaluating the
217 217 ``user_expressions`` will result in a simple error message in the return
218 218 fields of the form::
219 219
220 220 [ERROR] ExceptionType: Exception message
221 221
222 222 The user can simply send the same variable name or expression for
223 223 evaluation to see a regular traceback.
224 224
225 225 Execution counter (old prompt number)
226 226 The kernel has a single, monotonically increasing counter of all execution
227 227 requests that are made with ``silent=False``. This counter is used to
228 228 populate the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will
229 229 likely want to display it in some form to the user, which will typically
230 230 (but not necessarily) be done in the prompts. The value of this counter
231 231 will be returned as the ``execution_count`` field of all ``execute_reply```
232 232 messages.
233 233
234 234 Message type: ``execute_reply``::
235 235
236 236 content = {
237 237 # One of: 'ok' OR 'error' OR 'abort'
238 238 'status' : str,
239 239
240 240 # The global kernel counter that increases by one with each non-silent
241 241 # executed request. This will typically be used by clients to display
242 242 # prompt numbers to the user. If the request was a silent one, this will
243 243 # be the current value of the counter in the kernel.
244 244 'execution_count' : int,
245 245 }
246 246
247 247 When status is 'ok', the following extra fields are present::
248 248
249 249 {
250 250 # The execution payload is a dict with string keys that may have been
251 251 # produced by the code being executed. It is retrieved by the kernel at
252 252 # the end of the execution and sent back to the front end, which can take
253 253 # action on it as needed. See main text for further details.
254 254 'payload' : dict,
255 255
256 256 # Results for the user_variables and user_expressions.
257 257 'user_variables' : dict,
258 258 'user_expressions' : dict,
259 259
260 260 # The kernel will often transform the input provided to it. If the
261 261 # '---->' transform had been applied, this is filled, otherwise it's the
262 262 # empty string. So transformations like magics don't appear here, only
263 263 # autocall ones.
264 264 'transformed_code' : str,
265 265 }
266 266
267 267 .. admonition:: Execution payloads
268 268
269 269 The notion of an 'execution payload' is different from a return value of a
270 270 given set of code, which normally is just displayed on the pyout stream
271 271 through the PUB socket. The idea of a payload is to allow special types of
272 272 code, typically magics, to populate a data container in the IPython kernel
273 273 that will be shipped back to the caller via this channel. The kernel will
274 274 have an API for this, probably something along the lines of::
275 275
276 276 ip.exec_payload_add(key, value)
277 277
278 278 though this API is still in the design stages. The data returned in this
279 279 payload will allow frontends to present special views of what just happened.
280 280
281 281
282 282 When status is 'error', the following extra fields are present::
283 283
284 284 {
285 285 'exc_name' : str, # Exception name, as a string
286 286 'exc_value' : str, # Exception value, as a string
287 287
288 288 # The traceback will contain a list of frames, represented each as a
289 289 # string. For now we'll stick to the existing design of ultraTB, which
290 290 # controls exception level of detail statefully. But eventually we'll
291 291 # want to grow into a model where more information is collected and
292 292 # packed into the traceback object, with clients deciding how little or
293 293 # how much of it to unpack. But for now, let's start with a simple list
294 294 # of strings, since that requires only minimal changes to ultratb as
295 295 # written.
296 296 'traceback' : list,
297 297 }
298 298
299 299
300 300 When status is 'abort', there are for now no additional data fields. This
301 301 happens when the kernel was interrupted by a signal.
302 302
303 303 Kernel attribute access
304 304 -----------------------
305 305
306 306 While this protocol does not specify full RPC access to arbitrary methods of
307 307 the kernel object, the kernel does allow read (and in some cases write) access
308 308 to certain attributes.
309 309
310 310 The policy for which attributes can be read is: any attribute of the kernel, or
311 311 its sub-objects, that belongs to a :class:`Configurable` object and has been
312 312 declared at the class-level with Traits validation, is in principle accessible
313 313 as long as its name does not begin with a leading underscore. The attribute
314 314 itself will have metadata indicating whether it allows remote read and/or write
315 315 access. The message spec follows for attribute read and write requests.
316 316
317 317 Message type: ``getattr_request``::
318 318
319 319 content = {
320 320 # The (possibly dotted) name of the attribute
321 321 'name' : str,
322 322 }
323 323
324 324 When a ``getattr_request`` fails, there are two possible error types:
325 325
326 326 - AttributeError: this type of error was raised when trying to access the
327 327 given name by the kernel itself. This means that the attribute likely
328 328 doesn't exist.
329 329
330 330 - AccessError: the attribute exists but its value is not readable remotely.
331 331
332 332
333 333 Message type: ``getattr_reply``::
334 334
335 335 content = {
336 336 # One of ['ok', 'AttributeError', 'AccessError'].
337 337 'status' : str,
338 338 # If status is 'ok', a JSON object.
339 339 'value' : object,
340 340 }
341 341
342 342 Message type: ``setattr_request``::
343 343
344 344 content = {
345 345 # The (possibly dotted) name of the attribute
346 346 'name' : str,
347 347
348 348 # A JSON-encoded object, that will be validated by the Traits
349 349 # information in the kernel
350 350 'value' : object,
351 351 }
352 352
353 353 When a ``setattr_request`` fails, there are also two possible error types with
354 354 similar meanings as those of the ``getattr_request`` case, but for writing.
355 355
356 356 Message type: ``setattr_reply``::
357 357
358 358 content = {
359 359 # One of ['ok', 'AttributeError', 'AccessError'].
360 360 'status' : str,
361 361 }
362 362
363 363
364 364 Object information
365 365 ------------------
366 366
367 367 One of IPython's most used capabilities is the introspection of Python objects
368 368 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
369 369 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
370 370 enough that it warrants an explicit message type, especially because frontends
371 371 may want to get object information in response to user keystrokes (like Tab or
372 372 F1) besides from the user explicitly typing code like ``x??``.
373 373
374 374 Message type: ``object_info_request``::
375 375
376 376 content = {
377 377 # The (possibly dotted) name of the object to be searched in all
378 378 # relevant namespaces
379 379 'name' : str,
380 380
381 381 # The level of detail desired. The default (0) is equivalent to typing
382 382 # 'x?' at the prompt, 1 is equivalent to 'x??'.
383 383 'detail_level' : int,
384 384 }
385 385
386 386 The returned information will be a dictionary with keys very similar to the
387 387 field names that IPython prints at the terminal.
388 388
389 389 Message type: ``object_info_reply``::
390 390
391 391 content = {
392 392 # Boolean flag indicating whether the named object was found or not. If
393 393 # it's false, all other fields will be empty.
394 394 'found' : bool,
395 395
396 396 # Flags for magics and system aliases
397 397 'ismagic' : bool,
398 398 'isalias' : bool,
399 399
400 400 # The name of the namespace where the object was found ('builtin',
401 401 # 'magics', 'alias', 'interactive', etc.)
402 402 'namespace' : str,
403 403
404 404 # The type name will be type.__name__ for normal Python objects, but it
405 405 # can also be a string like 'Magic function' or 'System alias'
406 406 'type_name' : str,
407 407
408 408 'string_form' : str,
409 409
410 410 # For objects with a __class__ attribute this will be set
411 411 'base_class' : str,
412 412
413 413 # For objects with a __len__ attribute this will be set
414 414 'length' : int,
415 415
416 416 # If the object is a function, class or method whose file we can find,
417 417 # we give its full path
418 418 'file' : str,
419 419
420 420 # For pure Python callable objects, we can reconstruct the object
421 421 # definition line which provides its call signature. For convenience this
422 422 # is returned as a single 'definition' field, but below the raw parts that
423 423 # compose it are also returned as the argspec field.
424 424 'definition' : str,
425 425
426 426 # The individual parts that together form the definition string. Clients
427 427 # with rich display capabilities may use this to provide a richer and more
428 428 # precise representation of the definition line (e.g. by highlighting
429 429 # arguments based on the user's cursor position). For non-callable
430 430 # objects, this field is empty.
431 431 'argspec' : { # The names of all the arguments
432 432 args : list,
433 433 # The name of the varargs (*args), if any
434 434 varargs : str,
435 435 # The name of the varkw (**kw), if any
436 436 varkw : str,
437 437 # The values (as strings) of all default arguments. Note
438 438 # that these must be matched *in reverse* with the 'args'
439 439 # list above, since the first positional args have no default
440 440 # value at all.
441 441 func_defaults : list,
442 442 },
443 443
444 444 # For instances, provide the constructor signature (the definition of
445 445 # the __init__ method):
446 446 'init_definition' : str,
447 447
448 448 # Docstrings: for any object (function, method, module, package) with a
449 449 # docstring, we show it. But in addition, we may provide additional
450 450 # docstrings. For example, for instances we will show the constructor
451 451 # and class docstrings as well, if available.
452 452 'docstring' : str,
453 453
454 454 # For instances, provide the constructor and class docstrings
455 455 'init_docstring' : str,
456 456 'class_docstring' : str,
457 457
458 458 # If it's a callable object whose call method has a separate docstring and
459 459 # definition line:
460 460 'call_def' : str,
461 461 'call_docstring' : str,
462 462
463 463 # If detail_level was 1, we also try to find the source code that
464 464 # defines the object, if possible. The string 'None' will indicate
465 465 # that no source was found.
466 466 'source' : str,
467 467 }
468 468 '
469 469
470 470 Complete
471 471 --------
472 472
473 473 Message type: ``complete_request``::
474 474
475 475 content = {
476 476 # The text to be completed, such as 'a.is'
477 477 'text' : str,
478 478
479 479 # The full line, such as 'print a.is'. This allows completers to
480 480 # make decisions that may require information about more than just the
481 481 # current word.
482 482 'line' : str,
483 483
484 484 # The entire block of text where the line is. This may be useful in the
485 485 # case of multiline completions where more context may be needed. Note: if
486 486 # in practice this field proves unnecessary, remove it to lighten the
487 487 # messages.
488 488
489 489 'block' : str,
490 490
491 491 # The position of the cursor where the user hit 'TAB' on the line.
492 492 'cursor_pos' : int,
493 493 }
494 494
495 495 Message type: ``complete_reply``::
496 496
497 497 content = {
498 498 # The list of all matches to the completion request, such as
499 499 # ['a.isalnum', 'a.isalpha'] for the above example.
500 500 'matches' : list
501 501 }
502 502
503 503
504 504 History
505 505 -------
506 506
507 507 For clients to explicitly request history from a kernel. The kernel has all
508 508 the actual execution history stored in a single location, so clients can
509 509 request it from the kernel when needed.
510 510
511 511 Message type: ``history_request``::
512 512
513 513 content = {
514 514
515 515 # If True, also return output history in the resulting dict.
516 516 'output' : bool,
517 517
518 518 # If True, return the raw input history, else the transformed input.
519 519 'raw' : bool,
520 520
521 521 # This parameter can be one of: A number, a pair of numbers, None
522 522 # If not given, last 40 are returned.
523 523 # - number n: return the last n entries.
524 524 # - pair n1, n2: return entries in the range(n1, n2).
525 525 # - None: return all history
526 526 'index' : n or (n1, n2) or None,
527 527 }
528 528
529 529 Message type: ``history_reply``::
530 530
531 531 content = {
532 532 # A dict with prompt numbers as keys and either (input, output) or input
533 533 # as the value depending on whether output was True or False,
534 534 # respectively.
535 535 'history' : dict,
536 536 }
537
538
539 Kernel shutdown
540 ---------------
541
542 The clients can request the kernel to shut itself down; this is used in
543 multiple cases:
544
545 - when the user chooses to close the client application via a menu or window
546 control.
547 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
548 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
549 IPythonQt client) to force a kernel restart to get a clean kernel without
550 losing client-side state like history or inlined figures.
551
552 The client sends a shutdown request to the kernel, and once it receives the
553 reply message (which is otherwise empty), it can assume that the kernel has
554 completed shutdown safely.
555
556 Upon their own shutdown, client applications will typically execute a last
557 minute sanity check and forcefully terminate any kernel that is still alive, to
558 avoid leaving stray processes in the user's machine.
559
560 For both shutdown request and reply, there is no actual content that needs to
561 be sent, so the content dict is empty.
562
563 Message type: ``shutdown_request``::
564
565 content = {
566 }
567
568 Message type: ``shutdown_reply``::
569
570 content = {
571 }
572
573 .. Note::
574
575 When the clients detect a dead kernel thanks to inactivity on the heartbeat
576 socket, they simply send a forceful process termination signal, since a dead
577 process is unlikely to respond in any useful way to messages.
537 578
579
538 580 Messages on the PUB/SUB socket
539 581 ==============================
540 582
541 583 Streams (stdout, stderr, etc)
542 584 ------------------------------
543 585
544 586 Message type: ``stream``::
545 587
546 588 content = {
547 589 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
548 590 'name' : str,
549 591
550 592 # The data is an arbitrary string to be written to that stream
551 593 'data' : str,
552 594 }
553 595
554 596 When a kernel receives a raw_input call, it should also broadcast it on the pub
555 597 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
556 598 to monitor/display kernel interactions and possibly replay them to their user
557 599 or otherwise expose them.
558 600
559 601 Python inputs
560 602 -------------
561 603
562 604 These messages are the re-broadcast of the ``execute_request``.
563 605
564 606 Message type: ``pyin``::
565 607
566 608 content = {
567 609 # Source code to be executed, one or more lines
568 610 'code' : str
569 611 }
570 612
571 613 Python outputs
572 614 --------------
573 615
574 616 When Python produces output from code that has been compiled in with the
575 617 'single' flag to :func:`compile`, any expression that produces a value (such as
576 618 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
577 619 this value whatever it wants. The default behavior of ``sys.displayhook`` in
578 620 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
579 621 the value as long as it is not ``None`` (which isn't printed at all). In our
580 622 case, the kernel instantiates as ``sys.displayhook`` an object which has
581 623 similar behavior, but which instead of printing to stdout, broadcasts these
582 624 values as ``pyout`` messages for clients to display appropriately.
583 625
584 626 Message type: ``pyout``::
585 627
586 628 content = {
587 629 # The data is typically the repr() of the object.
588 630 'data' : str,
589 631
590 632 # The counter for this execution is also provided so that clients can
591 633 # display it, since IPython automatically creates variables called _N (for
592 634 # prompt N).
593 635 'execution_count' : int,
594 636 }
595 637
596 638 Python errors
597 639 -------------
598 640
599 641 When an error occurs during code execution
600 642
601 643 Message type: ``pyerr``::
602 644
603 645 content = {
604 646 # Similar content to the execute_reply messages for the 'error' case,
605 647 # except the 'status' field is omitted.
606 648 }
607 649
608 650 Kernel crashes
609 651 --------------
610 652
611 653 When the kernel has an unexpected exception, caught by the last-resort
612 654 sys.excepthook, we should broadcast the crash handler's output before exiting.
613 655 This will allow clients to notice that a kernel died, inform the user and
614 656 propose further actions.
615 657
616 658 Message type: ``crash``::
617 659
618 660 content = {
619 661 # Similarly to the 'error' case for execute_reply messages, this will
620 662 # contain exc_name, exc_type and traceback fields.
621 663
622 664 # An additional field with supplementary information such as where to
623 665 # send the crash message
624 666 'info' : str,
625 667 }
626 668
627 669
628 670 Future ideas
629 671 ------------
630 672
631 673 Other potential message types, currently unimplemented, listed below as ideas.
632 674
633 675 Message type: ``file``::
634 676
635 677 content = {
636 678 'path' : 'cool.jpg',
637 679 'mimetype' : str,
638 680 'data' : str,
639 681 }
640 682
641 683
642 684 Messages on the REQ/REP socket
643 685 ==============================
644 686
645 687 This is a socket that goes in the opposite direction: from the kernel to a
646 688 *single* frontend, and its purpose is to allow ``raw_input`` and similar
647 689 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
648 690 client. For now we will keep these messages as simple as possible, since they
649 691 basically only mean to convey the ``raw_input(prompt)`` call.
650 692
651 693 Message type: ``input_request``::
652 694
653 695 content = { 'prompt' : str }
654 696
655 697 Message type: ``input_reply``::
656 698
657 699 content = { 'value' : str }
658 700
659 701 .. Note::
660 702
661 703 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
662 704 practice the kernel should behave like an interactive program. When a
663 705 program is opened on the console, the keyboard effectively takes over the
664 706 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
665 707 Since the IPython kernel effectively behaves like a console program (albeit
666 708 one whose "keyboard" is actually living in a separate process and
667 709 transported over the zmq connection), raw ``stdin`` isn't expected to be
668 710 available.
669 711
670 712
671 713 Heartbeat for kernels
672 714 =====================
673 715
674 716 Initially we had considered using messages like those above over ZMQ for a
675 717 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
676 718 alive at all, even if it may be busy executing user code). But this has the
677 719 problem that if the kernel is locked inside extension code, it wouldn't execute
678 720 the python heartbeat code. But it turns out that we can implement a basic
679 721 heartbeat with pure ZMQ, without using any Python messaging at all.
680 722
681 723 The monitor sends out a single zmq message (right now, it is a str of the
682 724 monitor's lifetime in seconds), and gets the same message right back, prefixed
683 725 with the zmq identity of the XREQ socket in the heartbeat process. This can be
684 726 a uuid, or even a full message, but there doesn't seem to be a need for packing
685 727 up a message when the sender and receiver are the exact same Python object.
686 728
687 729 The model is this::
688 730
689 731 monitor.send(str(self.lifetime)) # '1.2345678910'
690 732
691 733 and the monitor receives some number of messages of the form::
692 734
693 735 ['uuid-abcd-dead-beef', '1.2345678910']
694 736
695 737 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
696 738 the rest is the message sent by the monitor. No Python code ever has any
697 739 access to the message between the monitor's send, and the monitor's recv.
698 740
699 741
700 742 ToDo
701 743 ====
702 744
703 745 Missing things include:
704 746
705 747 * Important: finish thinking through the payload concept and API.
706 748
707 749 * Important: ensure that we have a good solution for magics like %edit. It's
708 750 likely that with the payload concept we can build a full solution, but not
709 751 100% clear yet.
710 752
711 753 * Finishing the details of the heartbeat protocol.
712 754
713 755 * Signal handling: specify what kind of information kernel should broadcast (or
714 756 not) when it receives signals.
715 757
716 758 .. include:: ../links.rst
General Comments 0
You need to be logged in to leave comments. Login now