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