##// END OF EJS Templates
Implemented kernel interrupts for Windows.
epatters -
Show More
@@ -0,0 +1,125 b''
1 # Standard library imports.
2 import ctypes
3 import os
4 import time
5 from thread import interrupt_main
6 from threading import Thread
7
8 # Local imports.
9 from IPython.utils.io import raw_print
10
11
12 class ParentPollerUnix(Thread):
13 """ A Unix-specific daemon thread that terminates the program immediately
14 when the parent process no longer exists.
15 """
16
17 def __init__(self):
18 super(ParentPollerUnix, self).__init__()
19 self.daemon = True
20
21 def run(self):
22 # We cannot use os.waitpid because it works only for child processes.
23 from errno import EINTR
24 while True:
25 try:
26 if os.getppid() == 1:
27 raw_print('Killed by parent poller!')
28 os._exit(1)
29 time.sleep(1.0)
30 except OSError, e:
31 if e.errno == EINTR:
32 continue
33 raise
34
35
36 class ParentPollerWindows(Thread):
37 """ A Windows-specific daemon thread that listens for a special event that
38 signals an interrupt and, optionally, terminates the program immediately
39 when the parent process no longer exists.
40 """
41
42 def __init__(self, interrupt_handle=None, parent_handle=None):
43 """ Create the poller. At least one of the optional parameters must be
44 provided.
45
46 Parameters:
47 -----------
48 interrupt_handle : HANDLE (int), optional
49 If provided, the program will generate a Ctrl+C event when this
50 handle is signaled.
51
52 parent_handle : HANDLE (int), optional
53 If provided, the program will terminate immediately when this
54 handle is signaled.
55 """
56 assert(interrupt_handle or parent_handle)
57 super(ParentPollerWindows, self).__init__()
58 self.daemon = True
59 self.interrupt_handle = interrupt_handle
60 self.parent_handle = parent_handle
61
62 @staticmethod
63 def create_interrupt_event():
64 """ Create an interrupt event handle.
65
66 The parent process should use this static method for creating the
67 interrupt event that is passed to the child process. It should store
68 this handle and use it with ``send_interrupt`` to interrupt the child
69 process.
70 """
71 # Create a security attributes struct that permits inheritance of the
72 # handle by new processes.
73 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
74 class SECURITY_ATTRIBUTES(ctypes.Structure):
75 _fields_ = [ ("nLength", ctypes.c_int),
76 ("lpSecurityDescriptor", ctypes.c_void_p),
77 ("bInheritHandle", ctypes.c_int) ]
78 sa = SECURITY_ATTRIBUTES()
79 sa_p = ctypes.pointer(sa)
80 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
81 sa.lpSecurityDescriptor = 0
82 sa.bInheritHandle = 1
83
84 return ctypes.windll.kernel32.CreateEventA(
85 sa_p, # lpEventAttributes
86 False, # bManualReset
87 False, # bInitialState
88 '') # lpName
89
90 @staticmethod
91 def send_interrupt(interrupt_handle):
92 """ Sends an interrupt event using the specified handle.
93 """
94 ctypes.windll.kernel32.SetEvent(interrupt_handle)
95
96 def run(self):
97 """ Run the poll loop. This method never returns.
98 """
99 from _subprocess import WAIT_OBJECT_0, INFINITE
100
101 # Build the list of handle to listen on.
102 handles = []
103 if self.interrupt_handle:
104 handles.append(self.interrupt_handle)
105 if self.parent_handle:
106 handles.append(self.parent_handle)
107
108 # Listen forever.
109 while True:
110 result = ctypes.windll.kernel32.WaitForMultipleObjects(
111 len(handles), # nCount
112 (ctypes.c_int * len(handles))(*handles), # lpHandles
113 False, # bWaitAll
114 INFINITE) # dwMilliseconds
115
116 if WAIT_OBJECT_0 <= result < len(handles):
117 handle = handles[result - WAIT_OBJECT_0]
118
119 if handle == self.interrupt_handle:
120 raw_print('Interrupted by parent poller!')
121 interrupt_main()
122
123 elif handle == self.parent_handle:
124 raw_print('Killed by parent poller!')
125 os._exit(1)
@@ -1,546 +1,545 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import signal
6 import sys
5 import sys
7
6
8 # System library imports
7 # System library imports
9 from pygments.lexers import PythonLexer
8 from pygments.lexers import PythonLexer
10 from PyQt4 import QtCore, QtGui
9 from PyQt4 import QtCore, QtGui
11
10
12 # Local imports
11 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
12 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.utils.traitlets import Bool
14 from IPython.utils.traitlets import Bool
16 from bracket_matcher import BracketMatcher
15 from bracket_matcher import BracketMatcher
17 from call_tip_widget import CallTipWidget
16 from call_tip_widget import CallTipWidget
18 from completion_lexer import CompletionLexer
17 from completion_lexer import CompletionLexer
19 from history_console_widget import HistoryConsoleWidget
18 from history_console_widget import HistoryConsoleWidget
20 from pygments_highlighter import PygmentsHighlighter
19 from pygments_highlighter import PygmentsHighlighter
21
20
22
21
23 class FrontendHighlighter(PygmentsHighlighter):
22 class FrontendHighlighter(PygmentsHighlighter):
24 """ 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
25 prompts.
24 prompts.
26 """
25 """
27
26
28 def __init__(self, frontend):
27 def __init__(self, frontend):
29 super(FrontendHighlighter, self).__init__(frontend._control.document())
28 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 self._current_offset = 0
29 self._current_offset = 0
31 self._frontend = frontend
30 self._frontend = frontend
32 self.highlighting_on = False
31 self.highlighting_on = False
33
32
34 def highlightBlock(self, qstring):
33 def highlightBlock(self, qstring):
35 """ Highlight a block of text. Reimplemented to highlight selectively.
34 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """
35 """
37 if not self.highlighting_on:
36 if not self.highlighting_on:
38 return
37 return
39
38
40 # The input to this function is unicode string that may contain
39 # The input to this function is unicode string that may contain
41 # paragraph break characters, non-breaking spaces, etc. Here we acquire
40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # the string as plain text so we can compare it.
41 # the string as plain text so we can compare it.
43 current_block = self.currentBlock()
42 current_block = self.currentBlock()
44 string = self._frontend._get_block_plain_text(current_block)
43 string = self._frontend._get_block_plain_text(current_block)
45
44
46 # Decide whether to check for the regular or continuation prompt.
45 # Decide whether to check for the regular or continuation prompt.
47 if current_block.contains(self._frontend._prompt_pos):
46 if current_block.contains(self._frontend._prompt_pos):
48 prompt = self._frontend._prompt
47 prompt = self._frontend._prompt
49 else:
48 else:
50 prompt = self._frontend._continuation_prompt
49 prompt = self._frontend._continuation_prompt
51
50
52 # 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.
53 if string.startswith(prompt):
52 if string.startswith(prompt):
54 self._current_offset = len(prompt)
53 self._current_offset = len(prompt)
55 qstring.remove(0, len(prompt))
54 qstring.remove(0, len(prompt))
56 else:
55 else:
57 self._current_offset = 0
56 self._current_offset = 0
58
57
59 PygmentsHighlighter.highlightBlock(self, qstring)
58 PygmentsHighlighter.highlightBlock(self, qstring)
60
59
61 def rehighlightBlock(self, block):
60 def rehighlightBlock(self, block):
62 """ Reimplemented to temporarily enable highlighting if disabled.
61 """ Reimplemented to temporarily enable highlighting if disabled.
63 """
62 """
64 old = self.highlighting_on
63 old = self.highlighting_on
65 self.highlighting_on = True
64 self.highlighting_on = True
66 super(FrontendHighlighter, self).rehighlightBlock(block)
65 super(FrontendHighlighter, self).rehighlightBlock(block)
67 self.highlighting_on = old
66 self.highlighting_on = old
68
67
69 def setFormat(self, start, count, format):
68 def setFormat(self, start, count, format):
70 """ Reimplemented to highlight selectively.
69 """ Reimplemented to highlight selectively.
71 """
70 """
72 start += self._current_offset
71 start += self._current_offset
73 PygmentsHighlighter.setFormat(self, start, count, format)
72 PygmentsHighlighter.setFormat(self, start, count, format)
74
73
75
74
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 """ A Qt frontend for a generic Python kernel.
76 """ A Qt frontend for a generic Python kernel.
78 """
77 """
79
78
80 # An option and corresponding signal for overriding the default kernel
79 # An option and corresponding signal for overriding the default kernel
81 # interrupt behavior.
80 # interrupt behavior.
82 custom_interrupt = Bool(False)
81 custom_interrupt = Bool(False)
83 custom_interrupt_requested = QtCore.pyqtSignal()
82 custom_interrupt_requested = QtCore.pyqtSignal()
84
83
85 # An option and corresponding signals for overriding the default kernel
84 # An option and corresponding signals for overriding the default kernel
86 # restart behavior.
85 # restart behavior.
87 custom_restart = Bool(False)
86 custom_restart = Bool(False)
88 custom_restart_kernel_died = QtCore.pyqtSignal(float)
87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
89 custom_restart_requested = QtCore.pyqtSignal()
88 custom_restart_requested = QtCore.pyqtSignal()
90
89
91 # 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
92 # processed by the FrontendWidget.
91 # processed by the FrontendWidget.
93 executed = QtCore.pyqtSignal(object)
92 executed = QtCore.pyqtSignal(object)
94
93
95 # Emitted when an exit request has been received from the kernel.
94 # Emitted when an exit request has been received from the kernel.
96 exit_requested = QtCore.pyqtSignal()
95 exit_requested = QtCore.pyqtSignal()
97
96
98 # Protected class variables.
97 # Protected class variables.
99 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _input_splitter_class = InputSplitter
101 _input_splitter_class = InputSplitter
103
102
104 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
105 # 'object' interface
104 # 'object' interface
106 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
107
106
108 def __init__(self, *args, **kw):
107 def __init__(self, *args, **kw):
109 super(FrontendWidget, self).__init__(*args, **kw)
108 super(FrontendWidget, self).__init__(*args, **kw)
110
109
111 # FrontendWidget protected variables.
110 # FrontendWidget protected variables.
112 self._bracket_matcher = BracketMatcher(self._control)
111 self._bracket_matcher = BracketMatcher(self._control)
113 self._call_tip_widget = CallTipWidget(self._control)
112 self._call_tip_widget = CallTipWidget(self._control)
114 self._completion_lexer = CompletionLexer(PythonLexer())
113 self._completion_lexer = CompletionLexer(PythonLexer())
115 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
114 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
116 self._hidden = False
115 self._hidden = False
117 self._highlighter = FrontendHighlighter(self)
116 self._highlighter = FrontendHighlighter(self)
118 self._input_splitter = self._input_splitter_class(input_mode='cell')
117 self._input_splitter = self._input_splitter_class(input_mode='cell')
119 self._kernel_manager = None
118 self._kernel_manager = None
120 self._possible_kernel_restart = False
119 self._possible_kernel_restart = False
121 self._request_info = {}
120 self._request_info = {}
122
121
123 # Configure the ConsoleWidget.
122 # Configure the ConsoleWidget.
124 self.tab_width = 4
123 self.tab_width = 4
125 self._set_continuation_prompt('... ')
124 self._set_continuation_prompt('... ')
126
125
127 # Configure actions.
126 # Configure actions.
128 action = self._copy_raw_action
127 action = self._copy_raw_action
129 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
128 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
130 action.setEnabled(False)
129 action.setEnabled(False)
131 action.setShortcut(QtGui.QKeySequence(key))
130 action.setShortcut(QtGui.QKeySequence(key))
132 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
131 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
133 action.triggered.connect(self.copy_raw)
132 action.triggered.connect(self.copy_raw)
134 self.copy_available.connect(action.setEnabled)
133 self.copy_available.connect(action.setEnabled)
135 self.addAction(action)
134 self.addAction(action)
136
135
137 # Connect signal handlers.
136 # Connect signal handlers.
138 document = self._control.document()
137 document = self._control.document()
139 document.contentsChange.connect(self._document_contents_change)
138 document.contentsChange.connect(self._document_contents_change)
140
139
141 #---------------------------------------------------------------------------
140 #---------------------------------------------------------------------------
142 # 'ConsoleWidget' public interface
141 # 'ConsoleWidget' public interface
143 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
144
143
145 def copy(self):
144 def copy(self):
146 """ Copy the currently selected text to the clipboard, removing prompts.
145 """ Copy the currently selected text to the clipboard, removing prompts.
147 """
146 """
148 text = str(self._control.textCursor().selection().toPlainText())
147 text = str(self._control.textCursor().selection().toPlainText())
149 if text:
148 if text:
150 lines = map(transform_classic_prompt, text.splitlines())
149 lines = map(transform_classic_prompt, text.splitlines())
151 text = '\n'.join(lines)
150 text = '\n'.join(lines)
152 QtGui.QApplication.clipboard().setText(text)
151 QtGui.QApplication.clipboard().setText(text)
153
152
154 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
155 # 'ConsoleWidget' abstract interface
154 # 'ConsoleWidget' abstract interface
156 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
157
156
158 def _is_complete(self, source, interactive):
157 def _is_complete(self, source, interactive):
159 """ Returns whether 'source' can be completely processed and a new
158 """ Returns whether 'source' can be completely processed and a new
160 prompt created. When triggered by an Enter/Return key press,
159 prompt created. When triggered by an Enter/Return key press,
161 'interactive' is True; otherwise, it is False.
160 'interactive' is True; otherwise, it is False.
162 """
161 """
163 complete = self._input_splitter.push(source)
162 complete = self._input_splitter.push(source)
164 if interactive:
163 if interactive:
165 complete = not self._input_splitter.push_accepts_more()
164 complete = not self._input_splitter.push_accepts_more()
166 return complete
165 return complete
167
166
168 def _execute(self, source, hidden):
167 def _execute(self, source, hidden):
169 """ Execute 'source'. If 'hidden', do not show any output.
168 """ Execute 'source'. If 'hidden', do not show any output.
170
169
171 See parent class :meth:`execute` docstring for full details.
170 See parent class :meth:`execute` docstring for full details.
172 """
171 """
173 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
172 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
174 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
173 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
175 self._hidden = hidden
174 self._hidden = hidden
176
175
177 def _prompt_started_hook(self):
176 def _prompt_started_hook(self):
178 """ Called immediately after a new prompt is displayed.
177 """ Called immediately after a new prompt is displayed.
179 """
178 """
180 if not self._reading:
179 if not self._reading:
181 self._highlighter.highlighting_on = True
180 self._highlighter.highlighting_on = True
182
181
183 def _prompt_finished_hook(self):
182 def _prompt_finished_hook(self):
184 """ Called immediately after a prompt is finished, i.e. when some input
183 """ Called immediately after a prompt is finished, i.e. when some input
185 will be processed and a new prompt displayed.
184 will be processed and a new prompt displayed.
186 """
185 """
187 if not self._reading:
186 if not self._reading:
188 self._highlighter.highlighting_on = False
187 self._highlighter.highlighting_on = False
189
188
190 def _tab_pressed(self):
189 def _tab_pressed(self):
191 """ Called when the tab key is pressed. Returns whether to continue
190 """ Called when the tab key is pressed. Returns whether to continue
192 processing the event.
191 processing the event.
193 """
192 """
194 # Perform tab completion if:
193 # Perform tab completion if:
195 # 1) The cursor is in the input buffer.
194 # 1) The cursor is in the input buffer.
196 # 2) There is a non-whitespace character before the cursor.
195 # 2) There is a non-whitespace character before the cursor.
197 text = self._get_input_buffer_cursor_line()
196 text = self._get_input_buffer_cursor_line()
198 if text is None:
197 if text is None:
199 return False
198 return False
200 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
199 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
201 if complete:
200 if complete:
202 self._complete()
201 self._complete()
203 return not complete
202 return not complete
204
203
205 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
206 # 'ConsoleWidget' protected interface
205 # 'ConsoleWidget' protected interface
207 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
208
207
209 def _context_menu_make(self, pos):
208 def _context_menu_make(self, pos):
210 """ Reimplemented to add an action for raw copy.
209 """ Reimplemented to add an action for raw copy.
211 """
210 """
212 menu = super(FrontendWidget, self)._context_menu_make(pos)
211 menu = super(FrontendWidget, self)._context_menu_make(pos)
213 for before_action in menu.actions():
212 for before_action in menu.actions():
214 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
213 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
215 QtGui.QKeySequence.ExactMatch:
214 QtGui.QKeySequence.ExactMatch:
216 menu.insertAction(before_action, self._copy_raw_action)
215 menu.insertAction(before_action, self._copy_raw_action)
217 break
216 break
218 return menu
217 return menu
219
218
220 def _event_filter_console_keypress(self, event):
219 def _event_filter_console_keypress(self, event):
221 """ Reimplemented for execution interruption and smart backspace.
220 """ Reimplemented for execution interruption and smart backspace.
222 """
221 """
223 key = event.key()
222 key = event.key()
224 if self._control_key_down(event.modifiers(), include_command=False):
223 if self._control_key_down(event.modifiers(), include_command=False):
225
224
226 if key == QtCore.Qt.Key_C and self._executing:
225 if key == QtCore.Qt.Key_C and self._executing:
227 self.interrupt_kernel()
226 self.interrupt_kernel()
228 return True
227 return True
229
228
230 elif key == QtCore.Qt.Key_Period:
229 elif key == QtCore.Qt.Key_Period:
231 message = 'Are you sure you want to restart the kernel?'
230 message = 'Are you sure you want to restart the kernel?'
232 self.restart_kernel(message, instant_death=False)
231 self.restart_kernel(message, instant_death=False)
233 return True
232 return True
234
233
235 elif not event.modifiers() & QtCore.Qt.AltModifier:
234 elif not event.modifiers() & QtCore.Qt.AltModifier:
236
235
237 # Smart backspace: remove four characters in one backspace if:
236 # Smart backspace: remove four characters in one backspace if:
238 # 1) everything left of the cursor is whitespace
237 # 1) everything left of the cursor is whitespace
239 # 2) the four characters immediately left of the cursor are spaces
238 # 2) the four characters immediately left of the cursor are spaces
240 if key == QtCore.Qt.Key_Backspace:
239 if key == QtCore.Qt.Key_Backspace:
241 col = self._get_input_buffer_cursor_column()
240 col = self._get_input_buffer_cursor_column()
242 cursor = self._control.textCursor()
241 cursor = self._control.textCursor()
243 if col > 3 and not cursor.hasSelection():
242 if col > 3 and not cursor.hasSelection():
244 text = self._get_input_buffer_cursor_line()[:col]
243 text = self._get_input_buffer_cursor_line()[:col]
245 if text.endswith(' ') and not text.strip():
244 if text.endswith(' ') and not text.strip():
246 cursor.movePosition(QtGui.QTextCursor.Left,
245 cursor.movePosition(QtGui.QTextCursor.Left,
247 QtGui.QTextCursor.KeepAnchor, 4)
246 QtGui.QTextCursor.KeepAnchor, 4)
248 cursor.removeSelectedText()
247 cursor.removeSelectedText()
249 return True
248 return True
250
249
251 return super(FrontendWidget, self)._event_filter_console_keypress(event)
250 return super(FrontendWidget, self)._event_filter_console_keypress(event)
252
251
253 def _insert_continuation_prompt(self, cursor):
252 def _insert_continuation_prompt(self, cursor):
254 """ Reimplemented for auto-indentation.
253 """ Reimplemented for auto-indentation.
255 """
254 """
256 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
255 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
257 cursor.insertText(' ' * self._input_splitter.indent_spaces)
256 cursor.insertText(' ' * self._input_splitter.indent_spaces)
258
257
259 #---------------------------------------------------------------------------
258 #---------------------------------------------------------------------------
260 # 'BaseFrontendMixin' abstract interface
259 # 'BaseFrontendMixin' abstract interface
261 #---------------------------------------------------------------------------
260 #---------------------------------------------------------------------------
262
261
263 def _handle_complete_reply(self, rep):
262 def _handle_complete_reply(self, rep):
264 """ Handle replies for tab completion.
263 """ Handle replies for tab completion.
265 """
264 """
266 cursor = self._get_cursor()
265 cursor = self._get_cursor()
267 info = self._request_info.get('complete')
266 info = self._request_info.get('complete')
268 if info and info.id == rep['parent_header']['msg_id'] and \
267 if info and info.id == rep['parent_header']['msg_id'] and \
269 info.pos == cursor.position():
268 info.pos == cursor.position():
270 text = '.'.join(self._get_context())
269 text = '.'.join(self._get_context())
271 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
270 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
272 self._complete_with_items(cursor, rep['content']['matches'])
271 self._complete_with_items(cursor, rep['content']['matches'])
273
272
274 def _handle_execute_reply(self, msg):
273 def _handle_execute_reply(self, msg):
275 """ Handles replies for code execution.
274 """ Handles replies for code execution.
276 """
275 """
277 info = self._request_info.get('execute')
276 info = self._request_info.get('execute')
278 if info and info.id == msg['parent_header']['msg_id'] and \
277 if info and info.id == msg['parent_header']['msg_id'] and \
279 info.kind == 'user' and not self._hidden:
278 info.kind == 'user' and not self._hidden:
280 # Make sure that all output from the SUB channel has been processed
279 # Make sure that all output from the SUB channel has been processed
281 # before writing a new prompt.
280 # before writing a new prompt.
282 self.kernel_manager.sub_channel.flush()
281 self.kernel_manager.sub_channel.flush()
283
282
284 # Reset the ANSI style information to prevent bad text in stdout
283 # Reset the ANSI style information to prevent bad text in stdout
285 # from messing up our colors. We're not a true terminal so we're
284 # from messing up our colors. We're not a true terminal so we're
286 # allowed to do this.
285 # allowed to do this.
287 if self.ansi_codes:
286 if self.ansi_codes:
288 self._ansi_processor.reset_sgr()
287 self._ansi_processor.reset_sgr()
289
288
290 content = msg['content']
289 content = msg['content']
291 status = content['status']
290 status = content['status']
292 if status == 'ok':
291 if status == 'ok':
293 self._process_execute_ok(msg)
292 self._process_execute_ok(msg)
294 elif status == 'error':
293 elif status == 'error':
295 self._process_execute_error(msg)
294 self._process_execute_error(msg)
296 elif status == 'abort':
295 elif status == 'abort':
297 self._process_execute_abort(msg)
296 self._process_execute_abort(msg)
298
297
299 self._show_interpreter_prompt_for_reply(msg)
298 self._show_interpreter_prompt_for_reply(msg)
300 self.executed.emit(msg)
299 self.executed.emit(msg)
301
300
302 def _handle_input_request(self, msg):
301 def _handle_input_request(self, msg):
303 """ Handle requests for raw_input.
302 """ Handle requests for raw_input.
304 """
303 """
305 if self._hidden:
304 if self._hidden:
306 raise RuntimeError('Request for raw input during hidden execution.')
305 raise RuntimeError('Request for raw input during hidden execution.')
307
306
308 # Make sure that all output from the SUB channel has been processed
307 # Make sure that all output from the SUB channel has been processed
309 # before entering readline mode.
308 # before entering readline mode.
310 self.kernel_manager.sub_channel.flush()
309 self.kernel_manager.sub_channel.flush()
311
310
312 def callback(line):
311 def callback(line):
313 self.kernel_manager.rep_channel.input(line)
312 self.kernel_manager.rep_channel.input(line)
314 self._readline(msg['content']['prompt'], callback=callback)
313 self._readline(msg['content']['prompt'], callback=callback)
315
314
316 def _handle_kernel_died(self, since_last_heartbeat):
315 def _handle_kernel_died(self, since_last_heartbeat):
317 """ Handle the kernel's death by asking if the user wants to restart.
316 """ Handle the kernel's death by asking if the user wants to restart.
318 """
317 """
319 message = 'The kernel heartbeat has been inactive for %.2f ' \
318 message = 'The kernel heartbeat has been inactive for %.2f ' \
320 'seconds. Do you want to restart the kernel? You may ' \
319 'seconds. Do you want to restart the kernel? You may ' \
321 'first want to check the network connection.' % \
320 'first want to check the network connection.' % \
322 since_last_heartbeat
321 since_last_heartbeat
323 if self.custom_restart:
322 if self.custom_restart:
324 self.custom_restart_kernel_died.emit(since_last_heartbeat)
323 self.custom_restart_kernel_died.emit(since_last_heartbeat)
325 else:
324 else:
326 self.restart_kernel(message, instant_death=True)
325 self.restart_kernel(message, instant_death=True)
327
326
328 def _handle_object_info_reply(self, rep):
327 def _handle_object_info_reply(self, rep):
329 """ Handle replies for call tips.
328 """ Handle replies for call tips.
330 """
329 """
331 cursor = self._get_cursor()
330 cursor = self._get_cursor()
332 info = self._request_info.get('call_tip')
331 info = self._request_info.get('call_tip')
333 if info and info.id == rep['parent_header']['msg_id'] and \
332 if info and info.id == rep['parent_header']['msg_id'] and \
334 info.pos == cursor.position():
333 info.pos == cursor.position():
335 doc = rep['content']['docstring']
334 doc = rep['content']['docstring']
336 if doc:
335 if doc:
337 self._call_tip_widget.show_docstring(doc)
336 self._call_tip_widget.show_docstring(doc)
338
337
339 def _handle_pyout(self, msg):
338 def _handle_pyout(self, msg):
340 """ Handle display hook output.
339 """ Handle display hook output.
341 """
340 """
342 if not self._hidden and self._is_from_this_session(msg):
341 if not self._hidden and self._is_from_this_session(msg):
343 self._append_plain_text(msg['content']['data'] + '\n')
342 self._append_plain_text(msg['content']['data'] + '\n')
344
343
345 def _handle_stream(self, msg):
344 def _handle_stream(self, msg):
346 """ Handle stdout, stderr, and stdin.
345 """ Handle stdout, stderr, and stdin.
347 """
346 """
348 if not self._hidden and self._is_from_this_session(msg):
347 if not self._hidden and self._is_from_this_session(msg):
349 # Most consoles treat tabs as being 8 space characters. Convert tabs
348 # Most consoles treat tabs as being 8 space characters. Convert tabs
350 # to spaces so that output looks as expected regardless of this
349 # to spaces so that output looks as expected regardless of this
351 # widget's tab width.
350 # widget's tab width.
352 text = msg['content']['data'].expandtabs(8)
351 text = msg['content']['data'].expandtabs(8)
353
352
354 self._append_plain_text(text)
353 self._append_plain_text(text)
355 self._control.moveCursor(QtGui.QTextCursor.End)
354 self._control.moveCursor(QtGui.QTextCursor.End)
356
355
357 def _started_channels(self):
356 def _started_channels(self):
358 """ Called when the KernelManager channels have started listening or
357 """ Called when the KernelManager channels have started listening or
359 when the frontend is assigned an already listening KernelManager.
358 when the frontend is assigned an already listening KernelManager.
360 """
359 """
361 self._control.clear()
360 self._control.clear()
362 self._append_plain_text(self._get_banner())
361 self._append_plain_text(self._get_banner())
363 self._show_interpreter_prompt()
362 self._show_interpreter_prompt()
364
363
365 def _stopped_channels(self):
364 def _stopped_channels(self):
366 """ Called when the KernelManager channels have stopped listening or
365 """ Called when the KernelManager channels have stopped listening or
367 when a listening KernelManager is removed from the frontend.
366 when a listening KernelManager is removed from the frontend.
368 """
367 """
369 self._executing = self._reading = False
368 self._executing = self._reading = False
370 self._highlighter.highlighting_on = False
369 self._highlighter.highlighting_on = False
371
370
372 #---------------------------------------------------------------------------
371 #---------------------------------------------------------------------------
373 # 'FrontendWidget' public interface
372 # 'FrontendWidget' public interface
374 #---------------------------------------------------------------------------
373 #---------------------------------------------------------------------------
375
374
376 def copy_raw(self):
375 def copy_raw(self):
377 """ Copy the currently selected text to the clipboard without attempting
376 """ Copy the currently selected text to the clipboard without attempting
378 to remove prompts or otherwise alter the text.
377 to remove prompts or otherwise alter the text.
379 """
378 """
380 self._control.copy()
379 self._control.copy()
381
380
382 def execute_file(self, path, hidden=False):
381 def execute_file(self, path, hidden=False):
383 """ Attempts to execute file with 'path'. If 'hidden', no output is
382 """ Attempts to execute file with 'path'. If 'hidden', no output is
384 shown.
383 shown.
385 """
384 """
386 self.execute('execfile("%s")' % path, hidden=hidden)
385 self.execute('execfile("%s")' % path, hidden=hidden)
387
386
388 def interrupt_kernel(self):
387 def interrupt_kernel(self):
389 """ Attempts to interrupt the running kernel.
388 """ Attempts to interrupt the running kernel.
390 """
389 """
391 if self.custom_interrupt:
390 if self.custom_interrupt:
392 self.custom_interrupt_requested.emit()
391 self.custom_interrupt_requested.emit()
393 elif self.kernel_manager.has_kernel:
392 elif self.kernel_manager.has_kernel:
394 self.kernel_manager.signal_kernel(signal.SIGINT)
393 self.kernel_manager.interrupt_kernel()
395 else:
394 else:
396 self._append_plain_text('Kernel process is either remote or '
395 self._append_plain_text('Kernel process is either remote or '
397 'unspecified. Cannot interrupt.\n')
396 'unspecified. Cannot interrupt.\n')
398
397
399 def restart_kernel(self, message, instant_death=False):
398 def restart_kernel(self, message, instant_death=False):
400 """ Attempts to restart the running kernel.
399 """ Attempts to restart the running kernel.
401 """
400 """
402 # FIXME: instant_death should be configurable via a checkbox in the
401 # FIXME: instant_death should be configurable via a checkbox in the
403 # dialog. Right now at least the heartbeat path sets it to True and
402 # dialog. Right now at least the heartbeat path sets it to True and
404 # the manual restart to False. But those should just be the
403 # the manual restart to False. But those should just be the
405 # pre-selected states of a checkbox that the user could override if so
404 # pre-selected states of a checkbox that the user could override if so
406 # desired. But I don't know enough Qt to go implementing the checkbox
405 # desired. But I don't know enough Qt to go implementing the checkbox
407 # now.
406 # now.
408
407
409 # We want to make sure that if this dialog is already happening, that
408 # We want to make sure that if this dialog is already happening, that
410 # other signals don't trigger it again. This can happen when the
409 # other signals don't trigger it again. This can happen when the
411 # kernel_died heartbeat signal is emitted and the user is slow to
410 # kernel_died heartbeat signal is emitted and the user is slow to
412 # respond to the dialog.
411 # respond to the dialog.
413 if not self._possible_kernel_restart:
412 if not self._possible_kernel_restart:
414 if self.custom_restart:
413 if self.custom_restart:
415 self.custom_restart_requested.emit()
414 self.custom_restart_requested.emit()
416 elif self.kernel_manager.has_kernel:
415 elif self.kernel_manager.has_kernel:
417 # Setting this to True will prevent this logic from happening
416 # Setting this to True will prevent this logic from happening
418 # again until the current pass is completed.
417 # again until the current pass is completed.
419 self._possible_kernel_restart = True
418 self._possible_kernel_restart = True
420 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
419 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
421 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
420 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
422 message, buttons)
421 message, buttons)
423 if result == QtGui.QMessageBox.Yes:
422 if result == QtGui.QMessageBox.Yes:
424 try:
423 try:
425 self.kernel_manager.restart_kernel(
424 self.kernel_manager.restart_kernel(
426 instant_death=instant_death)
425 instant_death=instant_death)
427 except RuntimeError:
426 except RuntimeError:
428 message = 'Kernel started externally. Cannot restart.\n'
427 message = 'Kernel started externally. Cannot restart.\n'
429 self._append_plain_text(message)
428 self._append_plain_text(message)
430 else:
429 else:
431 self._stopped_channels()
430 self._stopped_channels()
432 self._append_plain_text('Kernel restarting...\n')
431 self._append_plain_text('Kernel restarting...\n')
433 self._show_interpreter_prompt()
432 self._show_interpreter_prompt()
434 # This might need to be moved to another location?
433 # This might need to be moved to another location?
435 self._possible_kernel_restart = False
434 self._possible_kernel_restart = False
436 else:
435 else:
437 self._append_plain_text('Kernel process is either remote or '
436 self._append_plain_text('Kernel process is either remote or '
438 'unspecified. Cannot restart.\n')
437 'unspecified. Cannot restart.\n')
439
438
440 #---------------------------------------------------------------------------
439 #---------------------------------------------------------------------------
441 # 'FrontendWidget' protected interface
440 # 'FrontendWidget' protected interface
442 #---------------------------------------------------------------------------
441 #---------------------------------------------------------------------------
443
442
444 def _call_tip(self):
443 def _call_tip(self):
445 """ Shows a call tip, if appropriate, at the current cursor location.
444 """ Shows a call tip, if appropriate, at the current cursor location.
446 """
445 """
447 # Decide if it makes sense to show a call tip
446 # Decide if it makes sense to show a call tip
448 cursor = self._get_cursor()
447 cursor = self._get_cursor()
449 cursor.movePosition(QtGui.QTextCursor.Left)
448 cursor.movePosition(QtGui.QTextCursor.Left)
450 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
449 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
451 return False
450 return False
452 context = self._get_context(cursor)
451 context = self._get_context(cursor)
453 if not context:
452 if not context:
454 return False
453 return False
455
454
456 # Send the metadata request to the kernel
455 # Send the metadata request to the kernel
457 name = '.'.join(context)
456 name = '.'.join(context)
458 msg_id = self.kernel_manager.xreq_channel.object_info(name)
457 msg_id = self.kernel_manager.xreq_channel.object_info(name)
459 pos = self._get_cursor().position()
458 pos = self._get_cursor().position()
460 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
459 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
461 return True
460 return True
462
461
463 def _complete(self):
462 def _complete(self):
464 """ Performs completion at the current cursor location.
463 """ Performs completion at the current cursor location.
465 """
464 """
466 context = self._get_context()
465 context = self._get_context()
467 if context:
466 if context:
468 # Send the completion request to the kernel
467 # Send the completion request to the kernel
469 msg_id = self.kernel_manager.xreq_channel.complete(
468 msg_id = self.kernel_manager.xreq_channel.complete(
470 '.'.join(context), # text
469 '.'.join(context), # text
471 self._get_input_buffer_cursor_line(), # line
470 self._get_input_buffer_cursor_line(), # line
472 self._get_input_buffer_cursor_column(), # cursor_pos
471 self._get_input_buffer_cursor_column(), # cursor_pos
473 self.input_buffer) # block
472 self.input_buffer) # block
474 pos = self._get_cursor().position()
473 pos = self._get_cursor().position()
475 info = self._CompletionRequest(msg_id, pos)
474 info = self._CompletionRequest(msg_id, pos)
476 self._request_info['complete'] = info
475 self._request_info['complete'] = info
477
476
478 def _get_banner(self):
477 def _get_banner(self):
479 """ Gets a banner to display at the beginning of a session.
478 """ Gets a banner to display at the beginning of a session.
480 """
479 """
481 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
480 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
482 '"license" for more information.'
481 '"license" for more information.'
483 return banner % (sys.version, sys.platform)
482 return banner % (sys.version, sys.platform)
484
483
485 def _get_context(self, cursor=None):
484 def _get_context(self, cursor=None):
486 """ Gets the context for the specified cursor (or the current cursor
485 """ Gets the context for the specified cursor (or the current cursor
487 if none is specified).
486 if none is specified).
488 """
487 """
489 if cursor is None:
488 if cursor is None:
490 cursor = self._get_cursor()
489 cursor = self._get_cursor()
491 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
490 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
492 QtGui.QTextCursor.KeepAnchor)
491 QtGui.QTextCursor.KeepAnchor)
493 text = str(cursor.selection().toPlainText())
492 text = str(cursor.selection().toPlainText())
494 return self._completion_lexer.get_context(text)
493 return self._completion_lexer.get_context(text)
495
494
496 def _process_execute_abort(self, msg):
495 def _process_execute_abort(self, msg):
497 """ Process a reply for an aborted execution request.
496 """ Process a reply for an aborted execution request.
498 """
497 """
499 self._append_plain_text("ERROR: execution aborted\n")
498 self._append_plain_text("ERROR: execution aborted\n")
500
499
501 def _process_execute_error(self, msg):
500 def _process_execute_error(self, msg):
502 """ Process a reply for an execution request that resulted in an error.
501 """ Process a reply for an execution request that resulted in an error.
503 """
502 """
504 content = msg['content']
503 content = msg['content']
505 traceback = ''.join(content['traceback'])
504 traceback = ''.join(content['traceback'])
506 self._append_plain_text(traceback)
505 self._append_plain_text(traceback)
507
506
508 def _process_execute_ok(self, msg):
507 def _process_execute_ok(self, msg):
509 """ Process a reply for a successful execution equest.
508 """ Process a reply for a successful execution equest.
510 """
509 """
511 payload = msg['content']['payload']
510 payload = msg['content']['payload']
512 for item in payload:
511 for item in payload:
513 if not self._process_execute_payload(item):
512 if not self._process_execute_payload(item):
514 warning = 'Warning: received unknown payload of type %s'
513 warning = 'Warning: received unknown payload of type %s'
515 print(warning % repr(item['source']))
514 print(warning % repr(item['source']))
516
515
517 def _process_execute_payload(self, item):
516 def _process_execute_payload(self, item):
518 """ Process a single payload item from the list of payload items in an
517 """ Process a single payload item from the list of payload items in an
519 execution reply. Returns whether the payload was handled.
518 execution reply. Returns whether the payload was handled.
520 """
519 """
521 # The basic FrontendWidget doesn't handle payloads, as they are a
520 # The basic FrontendWidget doesn't handle payloads, as they are a
522 # mechanism for going beyond the standard Python interpreter model.
521 # mechanism for going beyond the standard Python interpreter model.
523 return False
522 return False
524
523
525 def _show_interpreter_prompt(self):
524 def _show_interpreter_prompt(self):
526 """ Shows a prompt for the interpreter.
525 """ Shows a prompt for the interpreter.
527 """
526 """
528 self._show_prompt('>>> ')
527 self._show_prompt('>>> ')
529
528
530 def _show_interpreter_prompt_for_reply(self, msg):
529 def _show_interpreter_prompt_for_reply(self, msg):
531 """ Shows a prompt for the interpreter given an 'execute_reply' message.
530 """ Shows a prompt for the interpreter given an 'execute_reply' message.
532 """
531 """
533 self._show_interpreter_prompt()
532 self._show_interpreter_prompt()
534
533
535 #------ Signal handlers ----------------------------------------------------
534 #------ Signal handlers ----------------------------------------------------
536
535
537 def _document_contents_change(self, position, removed, added):
536 def _document_contents_change(self, position, removed, added):
538 """ Called whenever the document's content changes. Display a call tip
537 """ Called whenever the document's content changes. Display a call tip
539 if appropriate.
538 if appropriate.
540 """
539 """
541 # Calculate where the cursor should be *after* the change:
540 # Calculate where the cursor should be *after* the change:
542 position += added
541 position += added
543
542
544 document = self._control.document()
543 document = self._control.document()
545 if position == self._get_cursor().position():
544 if position == self._get_cursor().position():
546 self._call_tip()
545 self._call_tip()
@@ -1,239 +1,250 b''
1 """ Defines helper functions for creating kernel entry points and process
1 """ Defines helper functions for creating kernel entry points and process
2 launchers.
2 launchers.
3 """
3 """
4
4
5 # Standard library imports.
5 # Standard library imports.
6 import os
6 import os
7 import socket
7 import socket
8 from subprocess import Popen, PIPE
8 from subprocess import Popen, PIPE
9 import sys
9 import sys
10
10
11 # System library imports.
11 # System library imports.
12 import zmq
12 import zmq
13
13
14 # Local imports.
14 # Local imports.
15 from IPython.core.ultratb import FormattedTB
15 from IPython.core.ultratb import FormattedTB
16 from IPython.external.argparse import ArgumentParser
16 from IPython.external.argparse import ArgumentParser
17 from IPython.utils import io
17 from IPython.utils import io
18 from exitpoller import ExitPollerUnix, ExitPollerWindows
19 from displayhook import DisplayHook
18 from displayhook import DisplayHook
19 from heartbeat import Heartbeat
20 from iostream import OutStream
20 from iostream import OutStream
21 from parentpoller import ParentPollerUnix, ParentPollerWindows
21 from session import Session
22 from session import Session
22 from heartbeat import Heartbeat
23
23
24 def bind_port(socket, ip, port):
24 def bind_port(socket, ip, port):
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
26 chosen. Returns the port that was bound.
26 chosen. Returns the port that was bound.
27 """
27 """
28 connection = 'tcp://%s' % ip
28 connection = 'tcp://%s' % ip
29 if port <= 0:
29 if port <= 0:
30 port = socket.bind_to_random_port(connection)
30 port = socket.bind_to_random_port(connection)
31 else:
31 else:
32 connection += ':%i' % port
32 connection += ':%i' % port
33 socket.bind(connection)
33 socket.bind(connection)
34 return port
34 return port
35
35
36
36
37 def make_argument_parser():
37 def make_argument_parser():
38 """ Creates an ArgumentParser for the generic arguments supported by all
38 """ Creates an ArgumentParser for the generic arguments supported by all
39 kernel entry points.
39 kernel entry points.
40 """
40 """
41 parser = ArgumentParser()
41 parser = ArgumentParser()
42 parser.add_argument('--ip', type=str, default='127.0.0.1',
42 parser.add_argument('--ip', type=str, default='127.0.0.1',
43 help='set the kernel\'s IP address [default: local]')
43 help='set the kernel\'s IP address [default: local]')
44 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
44 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
45 help='set the XREP channel port [default: random]')
45 help='set the XREP channel port [default: random]')
46 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
46 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
47 help='set the PUB channel port [default: random]')
47 help='set the PUB channel port [default: random]')
48 parser.add_argument('--req', type=int, metavar='PORT', default=0,
48 parser.add_argument('--req', type=int, metavar='PORT', default=0,
49 help='set the REQ channel port [default: random]')
49 help='set the REQ channel port [default: random]')
50 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
50 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
51 help='set the heartbeat port [default: random]')
51 help='set the heartbeat port [default: random]')
52
52
53 if sys.platform == 'win32':
53 if sys.platform == 'win32':
54 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
55 default=0, help='interrupt this process when '
56 'HANDLE is signaled')
54 parser.add_argument('--parent', type=int, metavar='HANDLE',
57 parser.add_argument('--parent', type=int, metavar='HANDLE',
55 default=0, help='kill this process if the process '
58 default=0, help='kill this process if the process '
56 'with HANDLE dies')
59 'with HANDLE dies')
57 else:
60 else:
58 parser.add_argument('--parent', action='store_true',
61 parser.add_argument('--parent', action='store_true',
59 help='kill this process if its parent dies')
62 help='kill this process if its parent dies')
60
63
61 return parser
64 return parser
62
65
63
66
64 def make_kernel(namespace, kernel_factory,
67 def make_kernel(namespace, kernel_factory,
65 out_stream_factory=None, display_hook_factory=None):
68 out_stream_factory=None, display_hook_factory=None):
66 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
69 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
67 and exception handler.
70 and exception handler.
68 """
71 """
69 # If running under pythonw.exe, the interpreter will crash if more than 4KB
72 # If running under pythonw.exe, the interpreter will crash if more than 4KB
70 # of data is written to stdout or stderr. This is a bug that has been with
73 # of data is written to stdout or stderr. This is a bug that has been with
71 # Python for a very long time; see http://bugs.python.org/issue706263.
74 # Python for a very long time; see http://bugs.python.org/issue706263.
72 if sys.executable.endswith('pythonw.exe'):
75 if sys.executable.endswith('pythonw.exe'):
73 blackhole = file(os.devnull, 'w')
76 blackhole = file(os.devnull, 'w')
74 sys.stdout = sys.stderr = blackhole
77 sys.stdout = sys.stderr = blackhole
75 sys.__stdout__ = sys.__stderr__ = blackhole
78 sys.__stdout__ = sys.__stderr__ = blackhole
76
79
77 # Install minimal exception handling
80 # Install minimal exception handling
78 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
81 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
79 ostream=sys.__stdout__)
82 ostream=sys.__stdout__)
80
83
81 # Create a context, a session, and the kernel sockets.
84 # Create a context, a session, and the kernel sockets.
82 io.raw_print("Starting the kernel at pid:", os.getpid())
85 io.raw_print("Starting the kernel at pid:", os.getpid())
83 context = zmq.Context()
86 context = zmq.Context()
84 session = Session(username=u'kernel')
87 session = Session(username=u'kernel')
85
88
86 reply_socket = context.socket(zmq.XREP)
89 reply_socket = context.socket(zmq.XREP)
87 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
90 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
88 io.raw_print("XREP Channel on port", xrep_port)
91 io.raw_print("XREP Channel on port", xrep_port)
89
92
90 pub_socket = context.socket(zmq.PUB)
93 pub_socket = context.socket(zmq.PUB)
91 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
94 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
92 io.raw_print("PUB Channel on port", pub_port)
95 io.raw_print("PUB Channel on port", pub_port)
93
96
94 req_socket = context.socket(zmq.XREQ)
97 req_socket = context.socket(zmq.XREQ)
95 req_port = bind_port(req_socket, namespace.ip, namespace.req)
98 req_port = bind_port(req_socket, namespace.ip, namespace.req)
96 io.raw_print("REQ Channel on port", req_port)
99 io.raw_print("REQ Channel on port", req_port)
97
100
98 hb = Heartbeat(context, (namespace.ip, namespace.hb))
101 hb = Heartbeat(context, (namespace.ip, namespace.hb))
99 hb.start()
102 hb.start()
100 hb_port = hb.port
103 hb_port = hb.port
101 io.raw_print("Heartbeat REP Channel on port", hb_port)
104 io.raw_print("Heartbeat REP Channel on port", hb_port)
102
105
103 # Redirect input streams and set a display hook.
106 # Redirect input streams and set a display hook.
104 if out_stream_factory:
107 if out_stream_factory:
105 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
108 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
106 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
109 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
107 if display_hook_factory:
110 if display_hook_factory:
108 sys.displayhook = display_hook_factory(session, pub_socket)
111 sys.displayhook = display_hook_factory(session, pub_socket)
109
112
110 # Create the kernel.
113 # Create the kernel.
111 kernel = kernel_factory(session=session, reply_socket=reply_socket,
114 kernel = kernel_factory(session=session, reply_socket=reply_socket,
112 pub_socket=pub_socket, req_socket=req_socket)
115 pub_socket=pub_socket, req_socket=req_socket)
113 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
116 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
114 req_port=req_port, hb_port=hb_port)
117 req_port=req_port, hb_port=hb_port)
115 return kernel
118 return kernel
116
119
117
120
118 def start_kernel(namespace, kernel):
121 def start_kernel(namespace, kernel):
119 """ Starts a kernel.
122 """ Starts a kernel.
120 """
123 """
121 # Configure this kernel/process to die on parent termination, if necessary.
124 # Configure this kernel process to poll the parent process, if necessary.
122 if namespace.parent:
125 if sys.platform == 'win32':
123 if sys.platform == 'win32':
126 if namespace.interrupt or namespace.parent:
124 poller = ExitPollerWindows(namespace.parent)
127 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
125 else:
128 poller.start()
126 poller = ExitPollerUnix()
129 elif namespace.parent:
130 poller = ParentPollerUnix()
127 poller.start()
131 poller.start()
128
132
129 # Start the kernel mainloop.
133 # Start the kernel mainloop.
130 kernel.start()
134 kernel.start()
131
135
132
136
133 def make_default_main(kernel_factory):
137 def make_default_main(kernel_factory):
134 """ Creates the simplest possible kernel entry point.
138 """ Creates the simplest possible kernel entry point.
135 """
139 """
136 def main():
140 def main():
137 namespace = make_argument_parser().parse_args()
141 namespace = make_argument_parser().parse_args()
138 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
142 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
139 start_kernel(namespace, kernel)
143 start_kernel(namespace, kernel)
140 return main
144 return main
141
145
142
146
143 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
147 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
144 independent=False, extra_arguments=[]):
148 independent=False, extra_arguments=[]):
145 """ Launches a localhost kernel, binding to the specified ports.
149 """ Launches a localhost kernel, binding to the specified ports.
146
150
147 Parameters
151 Parameters
148 ----------
152 ----------
149 code : str,
153 code : str,
150 A string of Python code that imports and executes a kernel entry point.
154 A string of Python code that imports and executes a kernel entry point.
151
155
152 xrep_port : int, optional
156 xrep_port : int, optional
153 The port to use for XREP channel.
157 The port to use for XREP channel.
154
158
155 pub_port : int, optional
159 pub_port : int, optional
156 The port to use for the SUB channel.
160 The port to use for the SUB channel.
157
161
158 req_port : int, optional
162 req_port : int, optional
159 The port to use for the REQ (raw input) channel.
163 The port to use for the REQ (raw input) channel.
160
164
161 hb_port : int, optional
165 hb_port : int, optional
162 The port to use for the hearbeat REP channel.
166 The port to use for the hearbeat REP channel.
163
167
164 independent : bool, optional (default False)
168 independent : bool, optional (default False)
165 If set, the kernel process is guaranteed to survive if this process
169 If set, the kernel process is guaranteed to survive if this process
166 dies. If not set, an effort is made to ensure that the kernel is killed
170 dies. If not set, an effort is made to ensure that the kernel is killed
167 when this process dies. Note that in this case it is still good practice
171 when this process dies. Note that in this case it is still good practice
168 to kill kernels manually before exiting.
172 to kill kernels manually before exiting.
169
173
170 extra_arguments = list, optional
174 extra_arguments = list, optional
171 A list of extra arguments to pass when executing the launch code.
175 A list of extra arguments to pass when executing the launch code.
172
176
173 Returns
177 Returns
174 -------
178 -------
175 A tuple of form:
179 A tuple of form:
176 (kernel_process, xrep_port, pub_port, req_port)
180 (kernel_process, xrep_port, pub_port, req_port)
177 where kernel_process is a Popen object and the ports are integers.
181 where kernel_process is a Popen object and the ports are integers.
178 """
182 """
179 # Find open ports as necessary.
183 # Find open ports as necessary.
180 ports = []
184 ports = []
181 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
185 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
182 int(req_port <= 0) + int(hb_port <= 0)
186 int(req_port <= 0) + int(hb_port <= 0)
183 for i in xrange(ports_needed):
187 for i in xrange(ports_needed):
184 sock = socket.socket()
188 sock = socket.socket()
185 sock.bind(('', 0))
189 sock.bind(('', 0))
186 ports.append(sock)
190 ports.append(sock)
187 for i, sock in enumerate(ports):
191 for i, sock in enumerate(ports):
188 port = sock.getsockname()[1]
192 port = sock.getsockname()[1]
189 sock.close()
193 sock.close()
190 ports[i] = port
194 ports[i] = port
191 if xrep_port <= 0:
195 if xrep_port <= 0:
192 xrep_port = ports.pop(0)
196 xrep_port = ports.pop(0)
193 if pub_port <= 0:
197 if pub_port <= 0:
194 pub_port = ports.pop(0)
198 pub_port = ports.pop(0)
195 if req_port <= 0:
199 if req_port <= 0:
196 req_port = ports.pop(0)
200 req_port = ports.pop(0)
197 if hb_port <= 0:
201 if hb_port <= 0:
198 hb_port = ports.pop(0)
202 hb_port = ports.pop(0)
199
203
200 # Build the kernel launch command.
204 # Build the kernel launch command.
201 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
205 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
202 '--pub', str(pub_port), '--req', str(req_port),
206 '--pub', str(pub_port), '--req', str(req_port),
203 '--hb', str(hb_port) ]
207 '--hb', str(hb_port) ]
204 arguments.extend(extra_arguments)
208 arguments.extend(extra_arguments)
205
209
206 # Spawn a kernel.
210 # Spawn a kernel.
207 if sys.platform == 'win32':
211 if sys.platform == 'win32':
212 # Create a Win32 event for interrupting the kernel.
213 interrupt_event = ParentPollerWindows.create_interrupt_event()
214 arguments += [ '--interrupt', str(int(interrupt_event)) ]
208
215
209 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
216 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
210 # fail unless they are suitably redirected. We don't read from the
217 # fail unless they are suitably redirected. We don't read from the
211 # pipes, but they must exist.
218 # pipes, but they must exist.
212 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
219 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
213
220
214 if independent:
221 if independent:
215 proc = Popen(['start', '/b'] + arguments, shell=True,
222 proc = Popen(arguments,
223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
216 stdout=redirect, stderr=redirect, stdin=redirect)
224 stdout=redirect, stderr=redirect, stdin=redirect)
217 else:
225 else:
218 from _subprocess import DuplicateHandle, GetCurrentProcess, \
226 from _subprocess import DuplicateHandle, GetCurrentProcess, \
219 DUPLICATE_SAME_ACCESS
227 DUPLICATE_SAME_ACCESS
220 pid = GetCurrentProcess()
228 pid = GetCurrentProcess()
221 handle = DuplicateHandle(pid, pid, pid, 0,
229 handle = DuplicateHandle(pid, pid, pid, 0,
222 True, # Inheritable by new processes.
230 True, # Inheritable by new processes.
223 DUPLICATE_SAME_ACCESS)
231 DUPLICATE_SAME_ACCESS)
224 proc = Popen(arguments + ['--parent', str(int(handle))],
232 proc = Popen(arguments + ['--parent', str(int(handle))],
225 stdout=redirect, stderr=redirect, stdin=redirect)
233 stdout=redirect, stderr=redirect, stdin=redirect)
226
234
235 # Attach the interrupt event to the Popen objet so it can be used later.
236 proc.win32_interrupt_event = interrupt_event
237
227 # Clean up pipes created to work around Popen bug.
238 # Clean up pipes created to work around Popen bug.
228 if redirect is not None:
239 if redirect is not None:
229 proc.stdout.close()
240 proc.stdout.close()
230 proc.stderr.close()
241 proc.stderr.close()
231 proc.stdin.close()
242 proc.stdin.close()
232
243
233 else:
244 else:
234 if independent:
245 if independent:
235 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
246 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
236 else:
247 else:
237 proc = Popen(arguments + ['--parent'])
248 proc = Popen(arguments + ['--parent'])
238
249
239 return proc, xrep_port, pub_port, req_port, hb_port
250 return proc, xrep_port, pub_port, req_port, hb_port
@@ -1,867 +1,881 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 ====
5
6 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
7 """
5 """
8
6
9 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
11 #
9 #
12 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
15
13
16 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
17 # Imports
15 # Imports
18 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
19
17
20 # Standard library imports.
18 # Standard library imports.
21 from Queue import Queue, Empty
19 from Queue import Queue, Empty
22 from subprocess import Popen
20 from subprocess import Popen
21 import signal
23 import sys
22 import sys
24 from threading import Thread
23 from threading import Thread
25 import time
24 import time
26
25
27 # System library imports.
26 # System library imports.
28 import zmq
27 import zmq
29 from zmq import POLLIN, POLLOUT, POLLERR
28 from zmq import POLLIN, POLLOUT, POLLERR
30 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
31
30
32 # Local imports.
31 # Local imports.
33 from IPython.utils import io
32 from IPython.utils import io
34 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
35 from session import Session
34 from session import Session
36
35
37 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
38 # Constants and exceptions
37 # Constants and exceptions
39 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
40
39
41 LOCALHOST = '127.0.0.1'
40 LOCALHOST = '127.0.0.1'
42
41
43 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
44 pass
43 pass
45
44
46 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
47 # Utility functions
46 # Utility functions
48 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
49
48
50 # some utilities to validate message structure, these might get moved elsewhere
49 # some utilities to validate message structure, these might get moved elsewhere
51 # if they prove to have more generic utility
50 # if they prove to have more generic utility
52
51
53 def validate_string_list(lst):
52 def validate_string_list(lst):
54 """Validate that the input is a list of strings.
53 """Validate that the input is a list of strings.
55
54
56 Raises ValueError if not."""
55 Raises ValueError if not."""
57 if not isinstance(lst, list):
56 if not isinstance(lst, list):
58 raise ValueError('input %r must be a list' % lst)
57 raise ValueError('input %r must be a list' % lst)
59 for x in lst:
58 for x in lst:
60 if not isinstance(x, basestring):
59 if not isinstance(x, basestring):
61 raise ValueError('element %r in list must be a string' % x)
60 raise ValueError('element %r in list must be a string' % x)
62
61
63
62
64 def validate_string_dict(dct):
63 def validate_string_dict(dct):
65 """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.
66
65
67 Raises ValueError if not."""
66 Raises ValueError if not."""
68 for k,v in dct.iteritems():
67 for k,v in dct.iteritems():
69 if not isinstance(k, basestring):
68 if not isinstance(k, basestring):
70 raise ValueError('key %r in dict must be a string' % k)
69 raise ValueError('key %r in dict must be a string' % k)
71 if not isinstance(v, basestring):
70 if not isinstance(v, basestring):
72 raise ValueError('value %r in dict must be a string' % v)
71 raise ValueError('value %r in dict must be a string' % v)
73
72
74
73
75 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
76 # ZMQ Socket Channel classes
75 # ZMQ Socket Channel classes
77 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
78
77
79 class ZmqSocketChannel(Thread):
78 class ZmqSocketChannel(Thread):
80 """The base class for the channels that use ZMQ sockets.
79 """The base class for the channels that use ZMQ sockets.
81 """
80 """
82 context = None
81 context = None
83 session = None
82 session = None
84 socket = None
83 socket = None
85 ioloop = None
84 ioloop = None
86 iostate = None
85 iostate = None
87 _address = None
86 _address = None
88
87
89 def __init__(self, context, session, address):
88 def __init__(self, context, session, address):
90 """Create a channel
89 """Create a channel
91
90
92 Parameters
91 Parameters
93 ----------
92 ----------
94 context : :class:`zmq.Context`
93 context : :class:`zmq.Context`
95 The ZMQ context to use.
94 The ZMQ context to use.
96 session : :class:`session.Session`
95 session : :class:`session.Session`
97 The session to use.
96 The session to use.
98 address : tuple
97 address : tuple
99 Standard (ip, port) tuple that the kernel is listening on.
98 Standard (ip, port) tuple that the kernel is listening on.
100 """
99 """
101 super(ZmqSocketChannel, self).__init__()
100 super(ZmqSocketChannel, self).__init__()
102 self.daemon = True
101 self.daemon = True
103
102
104 self.context = context
103 self.context = context
105 self.session = session
104 self.session = session
106 if address[1] == 0:
105 if address[1] == 0:
107 message = 'The port number for a channel cannot be 0.'
106 message = 'The port number for a channel cannot be 0.'
108 raise InvalidPortNumber(message)
107 raise InvalidPortNumber(message)
109 self._address = address
108 self._address = address
110
109
111 def stop(self):
110 def stop(self):
112 """Stop the channel's activity.
111 """Stop the channel's activity.
113
112
114 This calls :method:`Thread.join` and returns when the thread
113 This calls :method:`Thread.join` and returns when the thread
115 terminates. :class:`RuntimeError` will be raised if
114 terminates. :class:`RuntimeError` will be raised if
116 :method:`self.start` is called again.
115 :method:`self.start` is called again.
117 """
116 """
118 self.join()
117 self.join()
119
118
120 @property
119 @property
121 def address(self):
120 def address(self):
122 """Get the channel's address as an (ip, port) tuple.
121 """Get the channel's address as an (ip, port) tuple.
123
122
124 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
125 port.
124 port.
126 """
125 """
127 return self._address
126 return self._address
128
127
129 def add_io_state(self, state):
128 def add_io_state(self, state):
130 """Add IO state to the eventloop.
129 """Add IO state to the eventloop.
131
130
132 Parameters
131 Parameters
133 ----------
132 ----------
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
133 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
135 The IO state flag to set.
134 The IO state flag to set.
136
135
137 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.
138 """
137 """
139 def add_io_state_callback():
138 def add_io_state_callback():
140 if not self.iostate & state:
139 if not self.iostate & state:
141 self.iostate = self.iostate | state
140 self.iostate = self.iostate | state
142 self.ioloop.update_handler(self.socket, self.iostate)
141 self.ioloop.update_handler(self.socket, self.iostate)
143 self.ioloop.add_callback(add_io_state_callback)
142 self.ioloop.add_callback(add_io_state_callback)
144
143
145 def drop_io_state(self, state):
144 def drop_io_state(self, state):
146 """Drop IO state from the eventloop.
145 """Drop IO state from the eventloop.
147
146
148 Parameters
147 Parameters
149 ----------
148 ----------
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
151 The IO state flag to set.
150 The IO state flag to set.
152
151
153 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.
154 """
153 """
155 def drop_io_state_callback():
154 def drop_io_state_callback():
156 if self.iostate & state:
155 if self.iostate & state:
157 self.iostate = self.iostate & (~state)
156 self.iostate = self.iostate & (~state)
158 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.update_handler(self.socket, self.iostate)
159 self.ioloop.add_callback(drop_io_state_callback)
158 self.ioloop.add_callback(drop_io_state_callback)
160
159
161
160
162 class XReqSocketChannel(ZmqSocketChannel):
161 class XReqSocketChannel(ZmqSocketChannel):
163 """The XREQ channel for issues request/replies to the kernel.
162 """The XREQ channel for issues request/replies to the kernel.
164 """
163 """
165
164
166 command_queue = None
165 command_queue = None
167
166
168 def __init__(self, context, session, address):
167 def __init__(self, context, session, address):
169 super(XReqSocketChannel, self).__init__(context, session, address)
168 super(XReqSocketChannel, self).__init__(context, session, address)
170 self.command_queue = Queue()
169 self.command_queue = Queue()
171 self.ioloop = ioloop.IOLoop()
170 self.ioloop = ioloop.IOLoop()
172
171
173 def run(self):
172 def run(self):
174 """The thread's main activity. Call start() instead."""
173 """The thread's main activity. Call start() instead."""
175 self.socket = self.context.socket(zmq.XREQ)
174 self.socket = self.context.socket(zmq.XREQ)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
175 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
177 self.socket.connect('tcp://%s:%i' % self.address)
176 self.socket.connect('tcp://%s:%i' % self.address)
178 self.iostate = POLLERR|POLLIN
177 self.iostate = POLLERR|POLLIN
179 self.ioloop.add_handler(self.socket, self._handle_events,
178 self.ioloop.add_handler(self.socket, self._handle_events,
180 self.iostate)
179 self.iostate)
181 self.ioloop.start()
180 self.ioloop.start()
182
181
183 def stop(self):
182 def stop(self):
184 self.ioloop.stop()
183 self.ioloop.stop()
185 super(XReqSocketChannel, self).stop()
184 super(XReqSocketChannel, self).stop()
186
185
187 def call_handlers(self, msg):
186 def call_handlers(self, msg):
188 """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.
189
188
190 Subclasses should override this method to handle incoming messages.
189 Subclasses should override this method to handle incoming messages.
191 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
192 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
193 handlers are called in the application thread.
192 handlers are called in the application thread.
194 """
193 """
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
194 raise NotImplementedError('call_handlers must be defined in a subclass.')
196
195
197 def execute(self, code, silent=False,
196 def execute(self, code, silent=False,
198 user_variables=None, user_expressions=None):
197 user_variables=None, user_expressions=None):
199 """Execute code in the kernel.
198 """Execute code in the kernel.
200
199
201 Parameters
200 Parameters
202 ----------
201 ----------
203 code : str
202 code : str
204 A string of Python code.
203 A string of Python code.
205
204
206 silent : bool, optional (default False)
205 silent : bool, optional (default False)
207 If set, the kernel will execute the code as quietly possible.
206 If set, the kernel will execute the code as quietly possible.
208
207
209 user_variables : list, optional
208 user_variables : list, optional
210 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
211 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
212 :func:`repr` as values.
211 :func:`repr` as values.
213
212
214 user_expressions : dict, optional
213 user_expressions : dict, optional
215 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
216 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
217 and their :func:`repr` as values.
216 and their :func:`repr` as values.
218
217
219 Returns
218 Returns
220 -------
219 -------
221 The msg_id of the message sent.
220 The msg_id of the message sent.
222 """
221 """
223 if user_variables is None:
222 if user_variables is None:
224 user_variables = []
223 user_variables = []
225 if user_expressions is None:
224 if user_expressions is None:
226 user_expressions = {}
225 user_expressions = {}
227
226
228 # Don't waste network traffic if inputs are invalid
227 # Don't waste network traffic if inputs are invalid
229 if not isinstance(code, basestring):
228 if not isinstance(code, basestring):
230 raise ValueError('code %r must be a string' % code)
229 raise ValueError('code %r must be a string' % code)
231 validate_string_list(user_variables)
230 validate_string_list(user_variables)
232 validate_string_dict(user_expressions)
231 validate_string_dict(user_expressions)
233
232
234 # Create class for content/msg creation. Related to, but possibly
233 # Create class for content/msg creation. Related to, but possibly
235 # not in Session.
234 # not in Session.
236 content = dict(code=code, silent=silent,
235 content = dict(code=code, silent=silent,
237 user_variables=user_variables,
236 user_variables=user_variables,
238 user_expressions=user_expressions)
237 user_expressions=user_expressions)
239 msg = self.session.msg('execute_request', content)
238 msg = self.session.msg('execute_request', content)
240 self._queue_request(msg)
239 self._queue_request(msg)
241 return msg['header']['msg_id']
240 return msg['header']['msg_id']
242
241
243 def complete(self, text, line, cursor_pos, block=None):
242 def complete(self, text, line, cursor_pos, block=None):
244 """Tab complete text in the kernel's namespace.
243 """Tab complete text in the kernel's namespace.
245
244
246 Parameters
245 Parameters
247 ----------
246 ----------
248 text : str
247 text : str
249 The text to complete.
248 The text to complete.
250 line : str
249 line : str
251 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
252 text to complete.
251 text to complete.
253 cursor_pos : int
252 cursor_pos : int
254 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
255 requested.
254 requested.
256 block : str, optional
255 block : str, optional
257 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.
258
257
259 Returns
258 Returns
260 -------
259 -------
261 The msg_id of the message sent.
260 The msg_id of the message sent.
262 """
261 """
263 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)
264 msg = self.session.msg('complete_request', content)
263 msg = self.session.msg('complete_request', content)
265 self._queue_request(msg)
264 self._queue_request(msg)
266 return msg['header']['msg_id']
265 return msg['header']['msg_id']
267
266
268 def object_info(self, oname):
267 def object_info(self, oname):
269 """Get metadata information about an object.
268 """Get metadata information about an object.
270
269
271 Parameters
270 Parameters
272 ----------
271 ----------
273 oname : str
272 oname : str
274 A string specifying the object name.
273 A string specifying the object name.
275
274
276 Returns
275 Returns
277 -------
276 -------
278 The msg_id of the message sent.
277 The msg_id of the message sent.
279 """
278 """
280 content = dict(oname=oname)
279 content = dict(oname=oname)
281 msg = self.session.msg('object_info_request', content)
280 msg = self.session.msg('object_info_request', content)
282 self._queue_request(msg)
281 self._queue_request(msg)
283 return msg['header']['msg_id']
282 return msg['header']['msg_id']
284
283
285 def history(self, index=None, raw=False, output=True):
284 def history(self, index=None, raw=False, output=True):
286 """Get the history list.
285 """Get the history list.
287
286
288 Parameters
287 Parameters
289 ----------
288 ----------
290 index : n or (n1, n2) or None
289 index : n or (n1, n2) or None
291 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
292 range(n1, n2). If None, then all entries. Raises IndexError if
291 range(n1, n2). If None, then all entries. Raises IndexError if
293 the format of index is incorrect.
292 the format of index is incorrect.
294 raw : bool
293 raw : bool
295 If True, return the raw input.
294 If True, return the raw input.
296 output : bool
295 output : bool
297 If True, then return the output as well.
296 If True, then return the output as well.
298
297
299 Returns
298 Returns
300 -------
299 -------
301 The msg_id of the message sent.
300 The msg_id of the message sent.
302 """
301 """
303 content = dict(index=index, raw=raw, output=output)
302 content = dict(index=index, raw=raw, output=output)
304 msg = self.session.msg('history_request', content)
303 msg = self.session.msg('history_request', content)
305 self._queue_request(msg)
304 self._queue_request(msg)
306 return msg['header']['msg_id']
305 return msg['header']['msg_id']
307
306
308 def shutdown(self):
307 def shutdown(self):
309 """Request an immediate kernel shutdown.
308 """Request an immediate kernel shutdown.
310
309
311 Upon receipt of the (empty) reply, client code can safely assume that
310 Upon receipt of the (empty) reply, client code can safely assume that
312 the kernel has shut down and it's safe to forcefully terminate it if
311 the kernel has shut down and it's safe to forcefully terminate it if
313 it's still alive.
312 it's still alive.
314
313
315 The kernel will send the reply via a function registered with Python's
314 The kernel will send the reply via a function registered with Python's
316 atexit module, ensuring it's truly done as the kernel is done with all
315 atexit module, ensuring it's truly done as the kernel is done with all
317 normal operation.
316 normal operation.
318 """
317 """
319 # Send quit message to kernel. Once we implement kernel-side setattr,
318 # Send quit message to kernel. Once we implement kernel-side setattr,
320 # this should probably be done that way, but for now this will do.
319 # this should probably be done that way, but for now this will do.
321 msg = self.session.msg('shutdown_request', {})
320 msg = self.session.msg('shutdown_request', {})
322 self._queue_request(msg)
321 self._queue_request(msg)
323 return msg['header']['msg_id']
322 return msg['header']['msg_id']
324
323
325 def _handle_events(self, socket, events):
324 def _handle_events(self, socket, events):
326 if events & POLLERR:
325 if events & POLLERR:
327 self._handle_err()
326 self._handle_err()
328 if events & POLLOUT:
327 if events & POLLOUT:
329 self._handle_send()
328 self._handle_send()
330 if events & POLLIN:
329 if events & POLLIN:
331 self._handle_recv()
330 self._handle_recv()
332
331
333 def _handle_recv(self):
332 def _handle_recv(self):
334 msg = self.socket.recv_json()
333 msg = self.socket.recv_json()
335 self.call_handlers(msg)
334 self.call_handlers(msg)
336
335
337 def _handle_send(self):
336 def _handle_send(self):
338 try:
337 try:
339 msg = self.command_queue.get(False)
338 msg = self.command_queue.get(False)
340 except Empty:
339 except Empty:
341 pass
340 pass
342 else:
341 else:
343 self.socket.send_json(msg)
342 self.socket.send_json(msg)
344 if self.command_queue.empty():
343 if self.command_queue.empty():
345 self.drop_io_state(POLLOUT)
344 self.drop_io_state(POLLOUT)
346
345
347 def _handle_err(self):
346 def _handle_err(self):
348 # 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.
349 raise zmq.ZMQError()
348 raise zmq.ZMQError()
350
349
351 def _queue_request(self, msg):
350 def _queue_request(self, msg):
352 self.command_queue.put(msg)
351 self.command_queue.put(msg)
353 self.add_io_state(POLLOUT)
352 self.add_io_state(POLLOUT)
354
353
355
354
356 class SubSocketChannel(ZmqSocketChannel):
355 class SubSocketChannel(ZmqSocketChannel):
357 """The SUB channel which listens for messages that the kernel publishes.
356 """The SUB channel which listens for messages that the kernel publishes.
358 """
357 """
359
358
360 def __init__(self, context, session, address):
359 def __init__(self, context, session, address):
361 super(SubSocketChannel, self).__init__(context, session, address)
360 super(SubSocketChannel, self).__init__(context, session, address)
362 self.ioloop = ioloop.IOLoop()
361 self.ioloop = ioloop.IOLoop()
363
362
364 def run(self):
363 def run(self):
365 """The thread's main activity. Call start() instead."""
364 """The thread's main activity. Call start() instead."""
366 self.socket = self.context.socket(zmq.SUB)
365 self.socket = self.context.socket(zmq.SUB)
367 self.socket.setsockopt(zmq.SUBSCRIBE,'')
366 self.socket.setsockopt(zmq.SUBSCRIBE,'')
368 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
367 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
369 self.socket.connect('tcp://%s:%i' % self.address)
368 self.socket.connect('tcp://%s:%i' % self.address)
370 self.iostate = POLLIN|POLLERR
369 self.iostate = POLLIN|POLLERR
371 self.ioloop.add_handler(self.socket, self._handle_events,
370 self.ioloop.add_handler(self.socket, self._handle_events,
372 self.iostate)
371 self.iostate)
373 self.ioloop.start()
372 self.ioloop.start()
374
373
375 def stop(self):
374 def stop(self):
376 self.ioloop.stop()
375 self.ioloop.stop()
377 super(SubSocketChannel, self).stop()
376 super(SubSocketChannel, self).stop()
378
377
379 def call_handlers(self, msg):
378 def call_handlers(self, msg):
380 """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.
381
380
382 Subclasses should override this method to handle incoming messages.
381 Subclasses should override this method to handle incoming messages.
383 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
384 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
385 handlers are called in the application thread.
384 handlers are called in the application thread.
386 """
385 """
387 raise NotImplementedError('call_handlers must be defined in a subclass.')
386 raise NotImplementedError('call_handlers must be defined in a subclass.')
388
387
389 def flush(self, timeout=1.0):
388 def flush(self, timeout=1.0):
390 """Immediately processes all pending messages on the SUB channel.
389 """Immediately processes all pending messages on the SUB channel.
391
390
392 Callers should use this method to ensure that :method:`call_handlers`
391 Callers should use this method to ensure that :method:`call_handlers`
393 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
394 0MQ SUB socket of this channel.
393 0MQ SUB socket of this channel.
395
394
396 This method is thread safe.
395 This method is thread safe.
397
396
398 Parameters
397 Parameters
399 ----------
398 ----------
400 timeout : float, optional
399 timeout : float, optional
401 The maximum amount of time to spend flushing, in seconds. The
400 The maximum amount of time to spend flushing, in seconds. The
402 default is one second.
401 default is one second.
403 """
402 """
404 # 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
405 # gets to perform at least one full poll.
404 # gets to perform at least one full poll.
406 stop_time = time.time() + timeout
405 stop_time = time.time() + timeout
407 for i in xrange(2):
406 for i in xrange(2):
408 self._flushed = False
407 self._flushed = False
409 self.ioloop.add_callback(self._flush)
408 self.ioloop.add_callback(self._flush)
410 while not self._flushed and time.time() < stop_time:
409 while not self._flushed and time.time() < stop_time:
411 time.sleep(0.01)
410 time.sleep(0.01)
412
411
413 def _handle_events(self, socket, events):
412 def _handle_events(self, socket, events):
414 # 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
415 if events & POLLERR:
414 if events & POLLERR:
416 self._handle_err()
415 self._handle_err()
417 if events & POLLIN:
416 if events & POLLIN:
418 self._handle_recv()
417 self._handle_recv()
419
418
420 def _handle_err(self):
419 def _handle_err(self):
421 # 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.
422 raise zmq.ZMQError()
421 raise zmq.ZMQError()
423
422
424 def _handle_recv(self):
423 def _handle_recv(self):
425 # Get all of the messages we can
424 # Get all of the messages we can
426 while True:
425 while True:
427 try:
426 try:
428 msg = self.socket.recv_json(zmq.NOBLOCK)
427 msg = self.socket.recv_json(zmq.NOBLOCK)
429 except zmq.ZMQError:
428 except zmq.ZMQError:
430 # Check the errno?
429 # Check the errno?
431 # Will this trigger POLLERR?
430 # Will this trigger POLLERR?
432 break
431 break
433 else:
432 else:
434 self.call_handlers(msg)
433 self.call_handlers(msg)
435
434
436 def _flush(self):
435 def _flush(self):
437 """Callback for :method:`self.flush`."""
436 """Callback for :method:`self.flush`."""
438 self._flushed = True
437 self._flushed = True
439
438
440
439
441 class RepSocketChannel(ZmqSocketChannel):
440 class RepSocketChannel(ZmqSocketChannel):
442 """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."""
443
442
444 msg_queue = None
443 msg_queue = None
445
444
446 def __init__(self, context, session, address):
445 def __init__(self, context, session, address):
447 super(RepSocketChannel, self).__init__(context, session, address)
446 super(RepSocketChannel, self).__init__(context, session, address)
448 self.ioloop = ioloop.IOLoop()
447 self.ioloop = ioloop.IOLoop()
449 self.msg_queue = Queue()
448 self.msg_queue = Queue()
450
449
451 def run(self):
450 def run(self):
452 """The thread's main activity. Call start() instead."""
451 """The thread's main activity. Call start() instead."""
453 self.socket = self.context.socket(zmq.XREQ)
452 self.socket = self.context.socket(zmq.XREQ)
454 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
453 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
455 self.socket.connect('tcp://%s:%i' % self.address)
454 self.socket.connect('tcp://%s:%i' % self.address)
456 self.iostate = POLLERR|POLLIN
455 self.iostate = POLLERR|POLLIN
457 self.ioloop.add_handler(self.socket, self._handle_events,
456 self.ioloop.add_handler(self.socket, self._handle_events,
458 self.iostate)
457 self.iostate)
459 self.ioloop.start()
458 self.ioloop.start()
460
459
461 def stop(self):
460 def stop(self):
462 self.ioloop.stop()
461 self.ioloop.stop()
463 super(RepSocketChannel, self).stop()
462 super(RepSocketChannel, self).stop()
464
463
465 def call_handlers(self, msg):
464 def call_handlers(self, msg):
466 """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.
467
466
468 Subclasses should override this method to handle incoming messages.
467 Subclasses should override this method to handle incoming messages.
469 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
470 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
471 handlers are called in the application thread.
470 handlers are called in the application thread.
472 """
471 """
473 raise NotImplementedError('call_handlers must be defined in a subclass.')
472 raise NotImplementedError('call_handlers must be defined in a subclass.')
474
473
475 def input(self, string):
474 def input(self, string):
476 """Send a string of raw input to the kernel."""
475 """Send a string of raw input to the kernel."""
477 content = dict(value=string)
476 content = dict(value=string)
478 msg = self.session.msg('input_reply', content)
477 msg = self.session.msg('input_reply', content)
479 self._queue_reply(msg)
478 self._queue_reply(msg)
480
479
481 def _handle_events(self, socket, events):
480 def _handle_events(self, socket, events):
482 if events & POLLERR:
481 if events & POLLERR:
483 self._handle_err()
482 self._handle_err()
484 if events & POLLOUT:
483 if events & POLLOUT:
485 self._handle_send()
484 self._handle_send()
486 if events & POLLIN:
485 if events & POLLIN:
487 self._handle_recv()
486 self._handle_recv()
488
487
489 def _handle_recv(self):
488 def _handle_recv(self):
490 msg = self.socket.recv_json()
489 msg = self.socket.recv_json()
491 self.call_handlers(msg)
490 self.call_handlers(msg)
492
491
493 def _handle_send(self):
492 def _handle_send(self):
494 try:
493 try:
495 msg = self.msg_queue.get(False)
494 msg = self.msg_queue.get(False)
496 except Empty:
495 except Empty:
497 pass
496 pass
498 else:
497 else:
499 self.socket.send_json(msg)
498 self.socket.send_json(msg)
500 if self.msg_queue.empty():
499 if self.msg_queue.empty():
501 self.drop_io_state(POLLOUT)
500 self.drop_io_state(POLLOUT)
502
501
503 def _handle_err(self):
502 def _handle_err(self):
504 # 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.
505 raise zmq.ZMQError()
504 raise zmq.ZMQError()
506
505
507 def _queue_reply(self, msg):
506 def _queue_reply(self, msg):
508 self.msg_queue.put(msg)
507 self.msg_queue.put(msg)
509 self.add_io_state(POLLOUT)
508 self.add_io_state(POLLOUT)
510
509
511
510
512 class HBSocketChannel(ZmqSocketChannel):
511 class HBSocketChannel(ZmqSocketChannel):
513 """The heartbeat channel which monitors the kernel heartbeat."""
512 """The heartbeat channel which monitors the kernel heartbeat."""
514
513
515 time_to_dead = 3.0
514 time_to_dead = 3.0
516 socket = None
515 socket = None
517 poller = None
516 poller = None
518 _running = None
517 _running = None
519 _pause = None
518 _pause = None
520
519
521 def __init__(self, context, session, address):
520 def __init__(self, context, session, address):
522 super(HBSocketChannel, self).__init__(context, session, address)
521 super(HBSocketChannel, self).__init__(context, session, address)
523 self._running = False
522 self._running = False
524 self._pause = False
523 self._pause = False
525
524
526 def _create_socket(self):
525 def _create_socket(self):
527 self.socket = self.context.socket(zmq.REQ)
526 self.socket = self.context.socket(zmq.REQ)
528 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
527 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
529 self.socket.connect('tcp://%s:%i' % self.address)
528 self.socket.connect('tcp://%s:%i' % self.address)
530 self.poller = zmq.Poller()
529 self.poller = zmq.Poller()
531 self.poller.register(self.socket, zmq.POLLIN)
530 self.poller.register(self.socket, zmq.POLLIN)
532
531
533 def run(self):
532 def run(self):
534 """The thread's main activity. Call start() instead."""
533 """The thread's main activity. Call start() instead."""
535 self._create_socket()
534 self._create_socket()
536 self._running = True
535 self._running = True
537 # Wait 2 seconds for the kernel to come up and the sockets to auto
536 # Wait 2 seconds for the kernel to come up and the sockets to auto
538 # connect. If we don't we will see the kernel as dead. Also, before
537 # connect. If we don't we will see the kernel as dead. Also, before
539 # the sockets are connected, the poller.poll line below is returning
538 # the sockets are connected, the poller.poll line below is returning
540 # too fast. This avoids that because the polling doesn't start until
539 # too fast. This avoids that because the polling doesn't start until
541 # after the sockets are connected.
540 # after the sockets are connected.
542 time.sleep(2.0)
541 time.sleep(2.0)
543 while self._running:
542 while self._running:
544 if self._pause:
543 if self._pause:
545 time.sleep(self.time_to_dead)
544 time.sleep(self.time_to_dead)
546 else:
545 else:
547 since_last_heartbeat = 0.0
546 since_last_heartbeat = 0.0
548 request_time = time.time()
547 request_time = time.time()
549 try:
548 try:
550 #io.rprint('Ping from HB channel') # dbg
549 #io.rprint('Ping from HB channel') # dbg
551 self.socket.send_json('ping')
550 self.socket.send_json('ping')
552 except zmq.ZMQError, e:
551 except zmq.ZMQError, e:
553 #io.rprint('*** HB Error:', e) # dbg
552 #io.rprint('*** HB Error:', e) # dbg
554 if e.errno == zmq.EFSM:
553 if e.errno == zmq.EFSM:
555 #io.rprint('sleep...', self.time_to_dead) # dbg
554 #io.rprint('sleep...', self.time_to_dead) # dbg
556 time.sleep(self.time_to_dead)
555 time.sleep(self.time_to_dead)
557 self._create_socket()
556 self._create_socket()
558 else:
557 else:
559 raise
558 raise
560 else:
559 else:
561 while True:
560 while True:
562 try:
561 try:
563 self.socket.recv_json(zmq.NOBLOCK)
562 self.socket.recv_json(zmq.NOBLOCK)
564 except zmq.ZMQError, e:
563 except zmq.ZMQError, e:
565 #io.rprint('*** HB Error 2:', e) # dbg
564 #io.rprint('*** HB Error 2:', e) # dbg
566 if e.errno == zmq.EAGAIN:
565 if e.errno == zmq.EAGAIN:
567 before_poll = time.time()
566 before_poll = time.time()
568 until_dead = self.time_to_dead - (before_poll -
567 until_dead = self.time_to_dead - (before_poll -
569 request_time)
568 request_time)
570
569
571 # When the return value of poll() is an empty list,
570 # When the return value of poll() is an empty list,
572 # that is when things have gone wrong (zeromq bug).
571 # that is when things have gone wrong (zeromq bug).
573 # As long as it is not an empty list, poll is
572 # As long as it is not an empty list, poll is
574 # working correctly even if it returns quickly.
573 # working correctly even if it returns quickly.
575 # Note: poll timeout is in milliseconds.
574 # Note: poll timeout is in milliseconds.
576 self.poller.poll(1000*until_dead)
575 self.poller.poll(1000*until_dead)
577
576
578 since_last_heartbeat = time.time() - request_time
577 since_last_heartbeat = time.time() - request_time
579 if since_last_heartbeat > self.time_to_dead:
578 if since_last_heartbeat > self.time_to_dead:
580 self.call_handlers(since_last_heartbeat)
579 self.call_handlers(since_last_heartbeat)
581 break
580 break
582 else:
581 else:
583 # FIXME: We should probably log this instead.
582 # FIXME: We should probably log this instead.
584 raise
583 raise
585 else:
584 else:
586 until_dead = self.time_to_dead - (time.time() -
585 until_dead = self.time_to_dead - (time.time() -
587 request_time)
586 request_time)
588 if until_dead > 0.0:
587 if until_dead > 0.0:
589 #io.rprint('sleep...', self.time_to_dead) # dbg
588 #io.rprint('sleep...', self.time_to_dead) # dbg
590 time.sleep(until_dead)
589 time.sleep(until_dead)
591 break
590 break
592
591
593 def pause(self):
592 def pause(self):
594 """Pause the heartbeat."""
593 """Pause the heartbeat."""
595 self._pause = True
594 self._pause = True
596
595
597 def unpause(self):
596 def unpause(self):
598 """Unpause the heartbeat."""
597 """Unpause the heartbeat."""
599 self._pause = False
598 self._pause = False
600
599
601 def is_beating(self):
600 def is_beating(self):
602 """Is the heartbeat running and not paused."""
601 """Is the heartbeat running and not paused."""
603 if self.is_alive() and not self._pause:
602 if self.is_alive() and not self._pause:
604 return True
603 return True
605 else:
604 else:
606 return False
605 return False
607
606
608 def stop(self):
607 def stop(self):
609 self._running = False
608 self._running = False
610 super(HBSocketChannel, self).stop()
609 super(HBSocketChannel, self).stop()
611
610
612 def call_handlers(self, since_last_heartbeat):
611 def call_handlers(self, since_last_heartbeat):
613 """This method is called in the ioloop thread when a message arrives.
612 """This method is called in the ioloop thread when a message arrives.
614
613
615 Subclasses should override this method to handle incoming messages.
614 Subclasses should override this method to handle incoming messages.
616 It is important to remember that this method is called in the thread
615 It is important to remember that this method is called in the thread
617 so that some logic must be done to ensure that the application leve
616 so that some logic must be done to ensure that the application leve
618 handlers are called in the application thread.
617 handlers are called in the application thread.
619 """
618 """
620 raise NotImplementedError('call_handlers must be defined in a subclass.')
619 raise NotImplementedError('call_handlers must be defined in a subclass.')
621
620
622
621
623 #-----------------------------------------------------------------------------
622 #-----------------------------------------------------------------------------
624 # Main kernel manager class
623 # Main kernel manager class
625 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
626
625
627 class KernelManager(HasTraits):
626 class KernelManager(HasTraits):
628 """ Manages a kernel for a frontend.
627 """ Manages a kernel for a frontend.
629
628
630 The SUB channel is for the frontend to receive messages published by the
629 The SUB channel is for the frontend to receive messages published by the
631 kernel.
630 kernel.
632
631
633 The REQ channel is for the frontend to make requests of the kernel.
632 The REQ channel is for the frontend to make requests of the kernel.
634
633
635 The REP channel is for the kernel to request stdin (raw_input) from the
634 The REP channel is for the kernel to request stdin (raw_input) from the
636 frontend.
635 frontend.
637 """
636 """
638 # The PyZMQ Context to use for communication with the kernel.
637 # The PyZMQ Context to use for communication with the kernel.
639 context = Instance(zmq.Context,(),{})
638 context = Instance(zmq.Context,(),{})
640
639
641 # The Session to use for communication with the kernel.
640 # The Session to use for communication with the kernel.
642 session = Instance(Session,(),{})
641 session = Instance(Session,(),{})
643
642
644 # The kernel process with which the KernelManager is communicating.
643 # The kernel process with which the KernelManager is communicating.
645 kernel = Instance(Popen)
644 kernel = Instance(Popen)
646
645
647 # The addresses for the communication channels.
646 # The addresses for the communication channels.
648 xreq_address = TCPAddress((LOCALHOST, 0))
647 xreq_address = TCPAddress((LOCALHOST, 0))
649 sub_address = TCPAddress((LOCALHOST, 0))
648 sub_address = TCPAddress((LOCALHOST, 0))
650 rep_address = TCPAddress((LOCALHOST, 0))
649 rep_address = TCPAddress((LOCALHOST, 0))
651 hb_address = TCPAddress((LOCALHOST, 0))
650 hb_address = TCPAddress((LOCALHOST, 0))
652
651
653 # The classes to use for the various channels.
652 # The classes to use for the various channels.
654 xreq_channel_class = Type(XReqSocketChannel)
653 xreq_channel_class = Type(XReqSocketChannel)
655 sub_channel_class = Type(SubSocketChannel)
654 sub_channel_class = Type(SubSocketChannel)
656 rep_channel_class = Type(RepSocketChannel)
655 rep_channel_class = Type(RepSocketChannel)
657 hb_channel_class = Type(HBSocketChannel)
656 hb_channel_class = Type(HBSocketChannel)
658
657
659 # Protected traits.
658 # Protected traits.
660 _launch_args = Any
659 _launch_args = Any
661 _xreq_channel = Any
660 _xreq_channel = Any
662 _sub_channel = Any
661 _sub_channel = Any
663 _rep_channel = Any
662 _rep_channel = Any
664 _hb_channel = Any
663 _hb_channel = Any
665
664
666 #--------------------------------------------------------------------------
665 #--------------------------------------------------------------------------
667 # Channel management methods:
666 # Channel management methods:
668 #--------------------------------------------------------------------------
667 #--------------------------------------------------------------------------
669
668
670 def start_channels(self, xreq=True, sub=True, rep=True):
669 def start_channels(self, xreq=True, sub=True, rep=True):
671 """Starts the channels for this kernel, but not the heartbeat.
670 """Starts the channels for this kernel, but not the heartbeat.
672
671
673 This will create the channels if they do not exist and then start
672 This will create the channels if they do not exist and then start
674 them. If port numbers of 0 are being used (random ports) then you
673 them. If port numbers of 0 are being used (random ports) then you
675 must first call :method:`start_kernel`. If the channels have been
674 must first call :method:`start_kernel`. If the channels have been
676 stopped and you call this, :class:`RuntimeError` will be raised.
675 stopped and you call this, :class:`RuntimeError` will be raised.
677 """
676 """
678 if xreq:
677 if xreq:
679 self.xreq_channel.start()
678 self.xreq_channel.start()
680 if sub:
679 if sub:
681 self.sub_channel.start()
680 self.sub_channel.start()
682 if rep:
681 if rep:
683 self.rep_channel.start()
682 self.rep_channel.start()
684
683
685 def stop_channels(self):
684 def stop_channels(self):
686 """Stops all the running channels for this kernel.
685 """Stops all the running channels for this kernel.
687 """
686 """
688 if self.xreq_channel.is_alive():
687 if self.xreq_channel.is_alive():
689 self.xreq_channel.stop()
688 self.xreq_channel.stop()
690 if self.sub_channel.is_alive():
689 if self.sub_channel.is_alive():
691 self.sub_channel.stop()
690 self.sub_channel.stop()
692 if self.rep_channel.is_alive():
691 if self.rep_channel.is_alive():
693 self.rep_channel.stop()
692 self.rep_channel.stop()
694
693
695 @property
694 @property
696 def channels_running(self):
695 def channels_running(self):
697 """Are any of the channels created and running?"""
696 """Are any of the channels created and running?"""
698 return self.xreq_channel.is_alive() \
697 return self.xreq_channel.is_alive() \
699 or self.sub_channel.is_alive() \
698 or self.sub_channel.is_alive() \
700 or self.rep_channel.is_alive()
699 or self.rep_channel.is_alive()
701
700
702 #--------------------------------------------------------------------------
701 #--------------------------------------------------------------------------
703 # Kernel process management methods:
702 # Kernel process management methods:
704 #--------------------------------------------------------------------------
703 #--------------------------------------------------------------------------
705
704
706 def start_kernel(self, **kw):
705 def start_kernel(self, **kw):
707 """Starts a kernel process and configures the manager to use it.
706 """Starts a kernel process and configures the manager to use it.
708
707
709 If random ports (port=0) are being used, this method must be called
708 If random ports (port=0) are being used, this method must be called
710 before the channels are created.
709 before the channels are created.
711
710
712 Parameters:
711 Parameters:
713 -----------
712 -----------
714 ipython : bool, optional (default True)
713 ipython : bool, optional (default True)
715 Whether to use an IPython kernel instead of a plain Python kernel.
714 Whether to use an IPython kernel instead of a plain Python kernel.
716 """
715 """
717 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
716 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
718 self.rep_address, self.hb_address
717 self.rep_address, self.hb_address
719 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
718 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
720 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
719 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
721 raise RuntimeError("Can only launch a kernel on localhost."
720 raise RuntimeError("Can only launch a kernel on localhost."
722 "Make sure that the '*_address' attributes are "
721 "Make sure that the '*_address' attributes are "
723 "configured properly.")
722 "configured properly.")
724
723
725 self._launch_args = kw.copy()
724 self._launch_args = kw.copy()
726 if kw.pop('ipython', True):
725 if kw.pop('ipython', True):
727 from ipkernel import launch_kernel
726 from ipkernel import launch_kernel
728 else:
727 else:
729 from pykernel import launch_kernel
728 from pykernel import launch_kernel
730 self.kernel, xrep, pub, req, hb = launch_kernel(
729 self.kernel, xrep, pub, req, hb = launch_kernel(
731 xrep_port=xreq[1], pub_port=sub[1],
730 xrep_port=xreq[1], pub_port=sub[1],
732 req_port=rep[1], hb_port=hb[1], **kw)
731 req_port=rep[1], hb_port=hb[1], **kw)
733 self.xreq_address = (LOCALHOST, xrep)
732 self.xreq_address = (LOCALHOST, xrep)
734 self.sub_address = (LOCALHOST, pub)
733 self.sub_address = (LOCALHOST, pub)
735 self.rep_address = (LOCALHOST, req)
734 self.rep_address = (LOCALHOST, req)
736 self.hb_address = (LOCALHOST, hb)
735 self.hb_address = (LOCALHOST, hb)
737
736
738 def shutdown_kernel(self):
737 def shutdown_kernel(self):
739 """ Attempts to the stop the kernel process cleanly. If the kernel
738 """ Attempts to the stop the kernel process cleanly. If the kernel
740 cannot be stopped, it is killed, if possible.
739 cannot be stopped, it is killed, if possible.
741 """
740 """
742 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
741 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
743 if sys.platform == 'win32':
742 if sys.platform == 'win32':
744 self.kill_kernel()
743 self.kill_kernel()
745 return
744 return
746
745
747 self.xreq_channel.shutdown()
746 self.xreq_channel.shutdown()
748 # Don't send any additional kernel kill messages immediately, to give
747 # Don't send any additional kernel kill messages immediately, to give
749 # the kernel a chance to properly execute shutdown actions. Wait for at
748 # the kernel a chance to properly execute shutdown actions. Wait for at
750 # most 1s, checking every 0.1s.
749 # most 1s, checking every 0.1s.
751 for i in range(10):
750 for i in range(10):
752 if self.is_alive:
751 if self.is_alive:
753 time.sleep(0.1)
752 time.sleep(0.1)
754 else:
753 else:
755 break
754 break
756 else:
755 else:
757 # OK, we've waited long enough.
756 # OK, we've waited long enough.
758 if self.has_kernel:
757 if self.has_kernel:
759 self.kill_kernel()
758 self.kill_kernel()
760
759
761 def restart_kernel(self, instant_death=False):
760 def restart_kernel(self, instant_death=False):
762 """Restarts a kernel with the same arguments that were used to launch
761 """Restarts a kernel with the same arguments that were used to launch
763 it. If the old kernel was launched with random ports, the same ports
762 it. If the old kernel was launched with random ports, the same ports
764 will be used for the new kernel.
763 will be used for the new kernel.
765
764
766 Parameters
765 Parameters
767 ----------
766 ----------
768 instant_death : bool, optional
767 instant_death : bool, optional
769 If True, the kernel is forcefully restarted *immediately*, without
768 If True, the kernel is forcefully restarted *immediately*, without
770 having a chance to do any cleanup action. Otherwise the kernel is
769 having a chance to do any cleanup action. Otherwise the kernel is
771 given 1s to clean up before a forceful restart is issued.
770 given 1s to clean up before a forceful restart is issued.
772
771
773 In all cases the kernel is restarted, the only difference is whether
772 In all cases the kernel is restarted, the only difference is whether
774 it is given a chance to perform a clean shutdown or not.
773 it is given a chance to perform a clean shutdown or not.
775 """
774 """
776 if self._launch_args is None:
775 if self._launch_args is None:
777 raise RuntimeError("Cannot restart the kernel. "
776 raise RuntimeError("Cannot restart the kernel. "
778 "No previous call to 'start_kernel'.")
777 "No previous call to 'start_kernel'.")
779 else:
778 else:
780 if self.has_kernel:
779 if self.has_kernel:
781 if instant_death:
780 if instant_death:
782 self.kill_kernel()
781 self.kill_kernel()
783 else:
782 else:
784 self.shutdown_kernel()
783 self.shutdown_kernel()
785 self.start_kernel(**self._launch_args)
784 self.start_kernel(**self._launch_args)
786
785
787 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
786 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
788 # unless there is some delay here.
787 # unless there is some delay here.
789 if sys.platform == 'win32':
788 if sys.platform == 'win32':
790 time.sleep(0.2)
789 time.sleep(0.2)
791
790
792 @property
791 @property
793 def has_kernel(self):
792 def has_kernel(self):
794 """Returns whether a kernel process has been specified for the kernel
793 """Returns whether a kernel process has been specified for the kernel
795 manager.
794 manager.
796 """
795 """
797 return self.kernel is not None
796 return self.kernel is not None
798
797
799 def kill_kernel(self):
798 def kill_kernel(self):
800 """ Kill the running kernel. """
799 """ Kill the running kernel. """
801 if self.has_kernel:
800 if self.has_kernel:
802 self.kernel.kill()
801 self.kernel.kill()
803 self.kernel = None
802 self.kernel = None
804 else:
803 else:
805 raise RuntimeError("Cannot kill kernel. No kernel is running!")
804 raise RuntimeError("Cannot kill kernel. No kernel is running!")
806
805
806 def interrupt_kernel(self):
807 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
808 well supported on all platforms.
809 """
810 if self.has_kernel:
811 if sys.platform == 'win32':
812 from parentpoller import ParentPollerWindows as Poller
813 Poller.send_interrupt(self.kernel.win32_interrupt_event)
814 else:
815 self.kernel.send_signal(signal.SIGINT)
816 else:
817 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
818
807 def signal_kernel(self, signum):
819 def signal_kernel(self, signum):
808 """ Sends a signal to the kernel. """
820 """ Sends a signal to the kernel. Note that since only SIGTERM is
821 supported on Windows, this function is only useful on Unix systems.
822 """
809 if self.has_kernel:
823 if self.has_kernel:
810 self.kernel.send_signal(signum)
824 self.kernel.send_signal(signum)
811 else:
825 else:
812 raise RuntimeError("Cannot signal kernel. No kernel is running!")
826 raise RuntimeError("Cannot signal kernel. No kernel is running!")
813
827
814 @property
828 @property
815 def is_alive(self):
829 def is_alive(self):
816 """Is the kernel process still running?"""
830 """Is the kernel process still running?"""
817 # FIXME: not using a heartbeat means this method is broken for any
831 # FIXME: not using a heartbeat means this method is broken for any
818 # remote kernel, it's only capable of handling local kernels.
832 # remote kernel, it's only capable of handling local kernels.
819 if self.has_kernel:
833 if self.has_kernel:
820 if self.kernel.poll() is None:
834 if self.kernel.poll() is None:
821 return True
835 return True
822 else:
836 else:
823 return False
837 return False
824 else:
838 else:
825 # We didn't start the kernel with this KernelManager so we don't
839 # We didn't start the kernel with this KernelManager so we don't
826 # know if it is running. We should use a heartbeat for this case.
840 # know if it is running. We should use a heartbeat for this case.
827 return True
841 return True
828
842
829 #--------------------------------------------------------------------------
843 #--------------------------------------------------------------------------
830 # Channels used for communication with the kernel:
844 # Channels used for communication with the kernel:
831 #--------------------------------------------------------------------------
845 #--------------------------------------------------------------------------
832
846
833 @property
847 @property
834 def xreq_channel(self):
848 def xreq_channel(self):
835 """Get the REQ socket channel object to make requests of the kernel."""
849 """Get the REQ socket channel object to make requests of the kernel."""
836 if self._xreq_channel is None:
850 if self._xreq_channel is None:
837 self._xreq_channel = self.xreq_channel_class(self.context,
851 self._xreq_channel = self.xreq_channel_class(self.context,
838 self.session,
852 self.session,
839 self.xreq_address)
853 self.xreq_address)
840 return self._xreq_channel
854 return self._xreq_channel
841
855
842 @property
856 @property
843 def sub_channel(self):
857 def sub_channel(self):
844 """Get the SUB socket channel object."""
858 """Get the SUB socket channel object."""
845 if self._sub_channel is None:
859 if self._sub_channel is None:
846 self._sub_channel = self.sub_channel_class(self.context,
860 self._sub_channel = self.sub_channel_class(self.context,
847 self.session,
861 self.session,
848 self.sub_address)
862 self.sub_address)
849 return self._sub_channel
863 return self._sub_channel
850
864
851 @property
865 @property
852 def rep_channel(self):
866 def rep_channel(self):
853 """Get the REP socket channel object to handle stdin (raw_input)."""
867 """Get the REP socket channel object to handle stdin (raw_input)."""
854 if self._rep_channel is None:
868 if self._rep_channel is None:
855 self._rep_channel = self.rep_channel_class(self.context,
869 self._rep_channel = self.rep_channel_class(self.context,
856 self.session,
870 self.session,
857 self.rep_address)
871 self.rep_address)
858 return self._rep_channel
872 return self._rep_channel
859
873
860 @property
874 @property
861 def hb_channel(self):
875 def hb_channel(self):
862 """Get the REP socket channel object to handle stdin (raw_input)."""
876 """Get the REP socket channel object to handle stdin (raw_input)."""
863 if self._hb_channel is None:
877 if self._hb_channel is None:
864 self._hb_channel = self.hb_channel_class(self.context,
878 self._hb_channel = self.hb_channel_class(self.context,
865 self.session,
879 self.session,
866 self.hb_address)
880 self.hb_address)
867 return self._hb_channel
881 return self._hb_channel
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now