##// END OF EJS Templates
* Improved frontend-side kernel restart support....
epatters -
Show More
@@ -32,7 +32,8 b' class BaseFrontendMixin(object):'
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.disconnect(self._dispatch)
35 old_manager.hb_channel.kernel_died.disconnect(self._handle_kernel_died)
35 old_manager.hb_channel.kernel_died.disconnect(
36 self._handle_kernel_died)
36
37
37 # Handle the case where the old kernel manager is still listening.
38 # Handle the case where the old kernel manager is still listening.
38 if old_manager.channels_running:
39 if old_manager.channels_running:
@@ -63,6 +64,19 b' class BaseFrontendMixin(object):'
63 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
64 # 'BaseFrontendMixin' abstract interface
65 # 'BaseFrontendMixin' abstract interface
65 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
67
68 def _handle_kernel_died(self, since_last_heartbeat):
69 """ This is called when the ``kernel_died`` signal is emitted.
70
71 This method is called when the kernel heartbeat has not been
72 active for a certain amount of time. The typical action will be to
73 give the user the option of restarting the kernel.
74
75 Parameters
76 ----------
77 since_last_heartbeat : float
78 The time since the heartbeat was last received.
79 """
66
80
67 def _started_channels(self):
81 def _started_channels(self):
68 """ Called when the KernelManager channels have started listening or
82 """ Called when the KernelManager channels have started listening or
@@ -93,17 +107,3 b' class BaseFrontendMixin(object):'
93 """
107 """
94 session = self._kernel_manager.session.session
108 session = self._kernel_manager.session.session
95 return msg['parent_header']['session'] == session
109 return msg['parent_header']['session'] == session
96
97 def _handle_kernel_died(self, since_last_heartbeat):
98 """ This is called when the ``kernel_died`` signal is emitted.
99
100 This method is called when the kernel heartbeat has not been
101 active for a certain amount of time. The typical action will be to
102 give the user the option of restarting the kernel.
103
104 Parameters
105 ----------
106 since_last_heartbeat : float
107 The time since the heartbeat was last received.
108 """
109 pass
@@ -79,9 +79,10 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
79 custom_interrupt = Bool(False)
79 custom_interrupt = Bool(False)
80 custom_interrupt_requested = QtCore.pyqtSignal()
80 custom_interrupt_requested = QtCore.pyqtSignal()
81
81
82 # An option and corresponding signal for overriding the default kernel
82 # An option and corresponding signals for overriding the default kernel
83 # restart behavior.
83 # restart behavior.
84 custom_restart = Bool(False)
84 custom_restart = Bool(False)
85 custom_restart_kernel_died = QtCore.pyqtSignal(float)
85 custom_restart_requested = QtCore.pyqtSignal()
86 custom_restart_requested = QtCore.pyqtSignal()
86
87
87 # Emitted when an 'execute_reply' has been received from the kernel and
88 # Emitted when an 'execute_reply' has been received from the kernel and
@@ -90,7 +91,6 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
90
91
91 # Protected class variables.
92 # Protected class variables.
92 _input_splitter_class = InputSplitter
93 _input_splitter_class = InputSplitter
93 _possible_kernel_restart = Bool(False)
94
94
95 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
96 # 'object' interface
96 # 'object' interface
@@ -107,6 +107,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
107 self._highlighter = FrontendHighlighter(self)
107 self._highlighter = FrontendHighlighter(self)
108 self._input_splitter = self._input_splitter_class(input_mode='block')
108 self._input_splitter = self._input_splitter_class(input_mode='block')
109 self._kernel_manager = None
109 self._kernel_manager = None
110 self._possible_kernel_restart = False
110
111
111 # Configure the ConsoleWidget.
112 # Configure the ConsoleWidget.
112 self.tab_width = 4
113 self.tab_width = 4
@@ -174,11 +175,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
174 key = event.key()
175 key = event.key()
175 if self._control_key_down(event.modifiers()):
176 if self._control_key_down(event.modifiers()):
176 if key == QtCore.Qt.Key_C and self._executing:
177 if key == QtCore.Qt.Key_C and self._executing:
177 self._kernel_interrupt()
178 self.interrupt_kernel()
178 return True
179 return True_
179 elif key == QtCore.Qt.Key_Period:
180 elif key == QtCore.Qt.Key_Period:
180 message = 'Are you sure you want to restart the kernel?'
181 message = 'Are you sure you want to restart the kernel?'
181 self._kernel_restart(message)
182 self.restart_kernel(message)
182 return True
183 return True
183 return super(FrontendWidget, self)._event_filter_console_keypress(event)
184 return super(FrontendWidget, self)._event_filter_console_keypress(event)
184
185
@@ -238,6 +239,18 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
238 self.kernel_manager.rep_channel.input(line)
239 self.kernel_manager.rep_channel.input(line)
239 self._readline(msg['content']['prompt'], callback=callback)
240 self._readline(msg['content']['prompt'], callback=callback)
240
241
242 def _handle_kernel_died(self, since_last_heartbeat):
243 """ Handle the kernel's death by asking if the user wants to restart.
244 """
245 message = 'The kernel heartbeat has been inactive for %.2f ' \
246 'seconds. Do you want to restart the kernel? You may ' \
247 'first want to check the network connection.' % \
248 since_last_heartbeat
249 if self.custom_restart:
250 self.custom_restart_kernel_died.emit(since_last_heartbeat)
251 else:
252 self.restart_kernel(message)
253
241 def _handle_object_info_reply(self, rep):
254 def _handle_object_info_reply(self, rep):
242 """ Handle replies for call tips.
255 """ Handle replies for call tips.
243 """
256 """
@@ -286,6 +299,50 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
286 """
299 """
287 self.execute('execfile("%s")' % path, hidden=hidden)
300 self.execute('execfile("%s")' % path, hidden=hidden)
288
301
302 def interrupt_kernel(self):
303 """ Attempts to interrupt the running kernel.
304 """
305 if self.custom_interrupt:
306 self.custom_interrupt_requested.emit()
307 elif self.kernel_manager.has_kernel:
308 self.kernel_manager.signal_kernel(signal.SIGINT)
309 else:
310 self._append_plain_text('Kernel process is either remote or '
311 'unspecified. Cannot interrupt.\n')
312
313 def restart_kernel(self, message):
314 """ Attempts to restart the running kernel.
315 """
316 # We want to make sure that if this dialog is already happening, that
317 # other signals don't trigger it again. This can happen when the
318 # kernel_died heartbeat signal is emitted and the user is slow to
319 # respond to the dialog.
320 if not self._possible_kernel_restart:
321 if self.custom_restart:
322 self.custom_restart_requested.emit()
323 elif self.kernel_manager.has_kernel:
324 # Setting this to True will prevent this logic from happening
325 # again until the current pass is completed.
326 self._possible_kernel_restart = True
327 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
328 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
329 message, buttons)
330 if result == QtGui.QMessageBox.Yes:
331 try:
332 self.kernel_manager.restart_kernel()
333 except RuntimeError:
334 message = 'Kernel started externally. Cannot restart.\n'
335 self._append_plain_text(message)
336 else:
337 self._stopped_channels()
338 self._append_plain_text('Kernel restarting...\n')
339 self._show_interpreter_prompt()
340 # This might need to be moved to another location?
341 self._possible_kernel_restart = False
342 else:
343 self._append_plain_text('Kernel process is either remote or '
344 'unspecified. Cannot restart.\n')
345
289 #---------------------------------------------------------------------------
346 #---------------------------------------------------------------------------
290 # 'FrontendWidget' protected interface
347 # 'FrontendWidget' protected interface
291 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
@@ -339,50 +396,6 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
339 text = str(cursor.selection().toPlainText())
396 text = str(cursor.selection().toPlainText())
340 return self._completion_lexer.get_context(text)
397 return self._completion_lexer.get_context(text)
341
398
342 def _kernel_interrupt(self):
343 """ Attempts to interrupt the running kernel.
344 """
345 if self.custom_interrupt:
346 self.custom_interrupt_requested.emit()
347 elif self.kernel_manager.has_kernel:
348 self.kernel_manager.signal_kernel(signal.SIGINT)
349 else:
350 self._append_plain_text('Kernel process is either remote or '
351 'unspecified. Cannot interrupt.\n')
352
353 def _kernel_restart(self, message):
354 """ Attempts to restart the running kernel.
355 """
356 # We want to make sure that if this dialog is already happening, that
357 # other signals don't trigger it again. This can happen when the
358 # kernel_died heartbeat signal is emitted and the user is slow to
359 # respond to the dialog.
360 if not self._possible_kernel_restart:
361 if self.custom_restart:
362 self.custom_restart_requested.emit()
363 elif self.kernel_manager.has_kernel:
364 # Setting this to True will prevent this logic from happening
365 # again until the current pass is completed.
366 self._possible_kernel_restart = True
367 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
368 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
369 message, buttons)
370 if result == QtGui.QMessageBox.Yes:
371 try:
372 self.kernel_manager.restart_kernel()
373 except RuntimeError:
374 message = 'Kernel started externally. Cannot restart.\n'
375 self._append_plain_text(message)
376 else:
377 self._stopped_channels()
378 self._append_plain_text('Kernel restarting...\n')
379 self._show_interpreter_prompt()
380 # This might need to be moved to another location?
381 self._possible_kernel_restart = False
382 else:
383 self._append_plain_text('Kernel process is either remote or '
384 'unspecified. Cannot restart.\n')
385
386 def _process_execute_abort(self, msg):
399 def _process_execute_abort(self, msg):
387 """ Process a reply for an aborted execution request.
400 """ Process a reply for an aborted execution request.
388 """
401 """
@@ -154,14 +154,6 b' class IPythonWidget(FrontendWidget):'
154 # FIXME: Disabled until history requests are properly implemented.
154 # FIXME: Disabled until history requests are properly implemented.
155 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
155 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
156
156
157 def _handle_kernel_died(self, since_last_heartbeat):
158 """ Handle the kernel's death by asking if the user wants to restart.
159 """
160 message = 'The kernel heartbeat has been inactive for %.2f ' \
161 'seconds. Do you want to restart the kernel? You may ' \
162 'first want to check the network connection.' % since_last_heartbeat
163 self._kernel_restart(message)
164
165 #---------------------------------------------------------------------------
157 #---------------------------------------------------------------------------
166 # 'FrontendWidget' interface
158 # 'FrontendWidget' interface
167 #---------------------------------------------------------------------------
159 #---------------------------------------------------------------------------
@@ -92,7 +92,6 b' def make_kernel(namespace, kernel_factory,'
92
92
93 # Redirect input streams and set a display hook.
93 # Redirect input streams and set a display hook.
94 if out_stream_factory:
94 if out_stream_factory:
95 pass
96 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
95 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
97 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
96 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
98 if display_hook_factory:
97 if display_hook_factory:
@@ -203,7 +202,7 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
203 DUPLICATE_SAME_ACCESS
202 DUPLICATE_SAME_ACCESS
204 pid = GetCurrentProcess()
203 pid = GetCurrentProcess()
205 handle = DuplicateHandle(pid, pid, pid, 0,
204 handle = DuplicateHandle(pid, pid, pid, 0,
206 True, # Inheritable by new processes.
205 True, # Inheritable by new processes.
207 DUPLICATE_SAME_ACCESS)
206 DUPLICATE_SAME_ACCESS)
208 proc = Popen(arguments + ['--parent', str(int(handle))])
207 proc = Popen(arguments + ['--parent', str(int(handle))])
209 else:
208 else:
@@ -437,7 +437,7 b" given, the GUI backend is matplotlib's, otherwise use one of: \\"
437 _kernel_classes = {
437 _kernel_classes = {
438 'qt' : QtKernel,
438 'qt' : QtKernel,
439 'qt4' : QtKernel,
439 'qt4' : QtKernel,
440 'payload-svg':Kernel,
440 'payload-svg': Kernel,
441 'wx' : WxKernel,
441 'wx' : WxKernel,
442 'tk' : TkKernel
442 'tk' : TkKernel
443 }
443 }
@@ -227,7 +227,8 b' class Kernel(HasTraits):'
227 # Kernel main and launch functions
227 # Kernel main and launch functions
228 #-----------------------------------------------------------------------------
228 #-----------------------------------------------------------------------------
229
229
230 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
230 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
231 independent=False):
231 """ Launches a localhost kernel, binding to the specified ports.
232 """ Launches a localhost kernel, binding to the specified ports.
232
233
233 Parameters
234 Parameters
@@ -241,6 +242,9 b' def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):'
241 req_port : int, optional
242 req_port : int, optional
242 The port to use for the REQ (raw input) channel.
243 The port to use for the REQ (raw input) channel.
243
244
245 hb_port : int, optional
246 The port to use for the hearbeat REP channel.
247
244 independent : bool, optional (default False)
248 independent : bool, optional (default False)
245 If set, the kernel process is guaranteed to survive if this process
249 If set, the kernel process is guaranteed to survive if this process
246 dies. If not set, an effort is made to ensure that the kernel is killed
250 dies. If not set, an effort is made to ensure that the kernel is killed
@@ -254,7 +258,8 b' def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):'
254 where kernel_process is a Popen object and the ports are integers.
258 where kernel_process is a Popen object and the ports are integers.
255 """
259 """
256 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
260 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
257 xrep_port, pub_port, req_port, independent)
261 xrep_port, pub_port, req_port, hb_port,
262 independent)
258
263
259 main = make_default_main(Kernel)
264 main = make_default_main(Kernel)
260
265
General Comments 0
You need to be logged in to leave comments. Login now