diff --git a/IPython/frontend/qt/base_frontend_mixin.py b/IPython/frontend/qt/base_frontend_mixin.py index 4b5372f..2e54e08 100644 --- a/IPython/frontend/qt/base_frontend_mixin.py +++ b/IPython/frontend/qt/base_frontend_mixin.py @@ -32,7 +32,8 @@ class BaseFrontendMixin(object): old_manager.sub_channel.message_received.disconnect(self._dispatch) old_manager.xreq_channel.message_received.disconnect(self._dispatch) old_manager.rep_channel.message_received.disconnect(self._dispatch) - old_manager.hb_channel.kernel_died.disconnect(self._handle_kernel_died) + old_manager.hb_channel.kernel_died.disconnect( + self._handle_kernel_died) # Handle the case where the old kernel manager is still listening. if old_manager.channels_running: @@ -63,6 +64,19 @@ class BaseFrontendMixin(object): #--------------------------------------------------------------------------- # 'BaseFrontendMixin' abstract interface #--------------------------------------------------------------------------- + + def _handle_kernel_died(self, since_last_heartbeat): + """ This is called when the ``kernel_died`` signal is emitted. + + This method is called when the kernel heartbeat has not been + active for a certain amount of time. The typical action will be to + give the user the option of restarting the kernel. + + Parameters + ---------- + since_last_heartbeat : float + The time since the heartbeat was last received. + """ def _started_channels(self): """ Called when the KernelManager channels have started listening or @@ -93,17 +107,3 @@ class BaseFrontendMixin(object): """ session = self._kernel_manager.session.session return msg['parent_header']['session'] == session - - def _handle_kernel_died(self, since_last_heartbeat): - """ This is called when the ``kernel_died`` signal is emitted. - - This method is called when the kernel heartbeat has not been - active for a certain amount of time. The typical action will be to - give the user the option of restarting the kernel. - - Parameters - ---------- - since_last_heartbeat : float - The time since the heartbeat was last received. - """ - pass diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 0acfb07..674a816 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -79,9 +79,10 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): custom_interrupt = Bool(False) custom_interrupt_requested = QtCore.pyqtSignal() - # An option and corresponding signal for overriding the default kernel + # An option and corresponding signals for overriding the default kernel # restart behavior. custom_restart = Bool(False) + custom_restart_kernel_died = QtCore.pyqtSignal(float) custom_restart_requested = QtCore.pyqtSignal() # Emitted when an 'execute_reply' has been received from the kernel and @@ -90,7 +91,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # Protected class variables. _input_splitter_class = InputSplitter - _possible_kernel_restart = Bool(False) #--------------------------------------------------------------------------- # 'object' interface @@ -107,6 +107,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self._highlighter = FrontendHighlighter(self) self._input_splitter = self._input_splitter_class(input_mode='block') self._kernel_manager = None + self._possible_kernel_restart = False # Configure the ConsoleWidget. self.tab_width = 4 @@ -174,11 +175,11 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): key = event.key() if self._control_key_down(event.modifiers()): if key == QtCore.Qt.Key_C and self._executing: - self._kernel_interrupt() - return True + self.interrupt_kernel() + return True_ elif key == QtCore.Qt.Key_Period: message = 'Are you sure you want to restart the kernel?' - self._kernel_restart(message) + self.restart_kernel(message) return True return super(FrontendWidget, self)._event_filter_console_keypress(event) @@ -238,6 +239,18 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self.kernel_manager.rep_channel.input(line) self._readline(msg['content']['prompt'], callback=callback) + def _handle_kernel_died(self, since_last_heartbeat): + """ Handle the kernel's death by asking if the user wants to restart. + """ + message = 'The kernel heartbeat has been inactive for %.2f ' \ + 'seconds. Do you want to restart the kernel? You may ' \ + 'first want to check the network connection.' % \ + since_last_heartbeat + if self.custom_restart: + self.custom_restart_kernel_died.emit(since_last_heartbeat) + else: + self.restart_kernel(message) + def _handle_object_info_reply(self, rep): """ Handle replies for call tips. """ @@ -286,6 +299,50 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ self.execute('execfile("%s")' % path, hidden=hidden) + def interrupt_kernel(self): + """ Attempts to interrupt the running kernel. + """ + if self.custom_interrupt: + self.custom_interrupt_requested.emit() + elif self.kernel_manager.has_kernel: + self.kernel_manager.signal_kernel(signal.SIGINT) + else: + self._append_plain_text('Kernel process is either remote or ' + 'unspecified. Cannot interrupt.\n') + + def restart_kernel(self, message): + """ Attempts to restart the running kernel. + """ + # We want to make sure that if this dialog is already happening, that + # other signals don't trigger it again. This can happen when the + # kernel_died heartbeat signal is emitted and the user is slow to + # respond to the dialog. + if not self._possible_kernel_restart: + if self.custom_restart: + self.custom_restart_requested.emit() + elif self.kernel_manager.has_kernel: + # Setting this to True will prevent this logic from happening + # again until the current pass is completed. + self._possible_kernel_restart = True + buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No + result = QtGui.QMessageBox.question(self, 'Restart kernel?', + message, buttons) + if result == QtGui.QMessageBox.Yes: + try: + self.kernel_manager.restart_kernel() + except RuntimeError: + message = 'Kernel started externally. Cannot restart.\n' + self._append_plain_text(message) + else: + self._stopped_channels() + self._append_plain_text('Kernel restarting...\n') + self._show_interpreter_prompt() + # This might need to be moved to another location? + self._possible_kernel_restart = False + else: + self._append_plain_text('Kernel process is either remote or ' + 'unspecified. Cannot restart.\n') + #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- @@ -339,50 +396,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): text = str(cursor.selection().toPlainText()) return self._completion_lexer.get_context(text) - def _kernel_interrupt(self): - """ Attempts to interrupt the running kernel. - """ - if self.custom_interrupt: - self.custom_interrupt_requested.emit() - elif self.kernel_manager.has_kernel: - self.kernel_manager.signal_kernel(signal.SIGINT) - else: - self._append_plain_text('Kernel process is either remote or ' - 'unspecified. Cannot interrupt.\n') - - def _kernel_restart(self, message): - """ Attempts to restart the running kernel. - """ - # We want to make sure that if this dialog is already happening, that - # other signals don't trigger it again. This can happen when the - # kernel_died heartbeat signal is emitted and the user is slow to - # respond to the dialog. - if not self._possible_kernel_restart: - if self.custom_restart: - self.custom_restart_requested.emit() - elif self.kernel_manager.has_kernel: - # Setting this to True will prevent this logic from happening - # again until the current pass is completed. - self._possible_kernel_restart = True - buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - result = QtGui.QMessageBox.question(self, 'Restart kernel?', - message, buttons) - if result == QtGui.QMessageBox.Yes: - try: - self.kernel_manager.restart_kernel() - except RuntimeError: - message = 'Kernel started externally. Cannot restart.\n' - self._append_plain_text(message) - else: - self._stopped_channels() - self._append_plain_text('Kernel restarting...\n') - self._show_interpreter_prompt() - # This might need to be moved to another location? - self._possible_kernel_restart = False - else: - self._append_plain_text('Kernel process is either remote or ' - 'unspecified. Cannot restart.\n') - def _process_execute_abort(self, msg): """ Process a reply for an aborted execution request. """ diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 7ecd7d8..0ee71c2 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -154,14 +154,6 @@ class IPythonWidget(FrontendWidget): # FIXME: Disabled until history requests are properly implemented. #self.kernel_manager.xreq_channel.history(raw=True, output=False) - def _handle_kernel_died(self, since_last_heartbeat): - """ Handle the kernel's death by asking if the user wants to restart. - """ - message = 'The kernel heartbeat has been inactive for %.2f ' \ - 'seconds. Do you want to restart the kernel? You may ' \ - 'first want to check the network connection.' % since_last_heartbeat - self._kernel_restart(message) - #--------------------------------------------------------------------------- # 'FrontendWidget' interface #--------------------------------------------------------------------------- diff --git a/IPython/zmq/entry_point.py b/IPython/zmq/entry_point.py index 870c027..459d1fb 100644 --- a/IPython/zmq/entry_point.py +++ b/IPython/zmq/entry_point.py @@ -92,7 +92,6 @@ def make_kernel(namespace, kernel_factory, # Redirect input streams and set a display hook. if out_stream_factory: - pass sys.stdout = out_stream_factory(session, pub_socket, u'stdout') sys.stderr = out_stream_factory(session, pub_socket, u'stderr') if display_hook_factory: @@ -203,7 +202,7 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0, DUPLICATE_SAME_ACCESS pid = GetCurrentProcess() handle = DuplicateHandle(pid, pid, pid, 0, - True, # Inheritable by new processes. + True, # Inheritable by new processes. DUPLICATE_SAME_ACCESS) proc = Popen(arguments + ['--parent', str(int(handle))]) else: diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index b08c9bf..ddb4541 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -437,7 +437,7 @@ given, the GUI backend is matplotlib's, otherwise use one of: \ _kernel_classes = { 'qt' : QtKernel, 'qt4' : QtKernel, - 'payload-svg':Kernel, + 'payload-svg': Kernel, 'wx' : WxKernel, 'tk' : TkKernel } diff --git a/IPython/zmq/pykernel.py b/IPython/zmq/pykernel.py index f6cb9b0..9f68ee6 100755 --- a/IPython/zmq/pykernel.py +++ b/IPython/zmq/pykernel.py @@ -227,7 +227,8 @@ class Kernel(HasTraits): # Kernel main and launch functions #----------------------------------------------------------------------------- -def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): +def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0, + independent=False): """ Launches a localhost kernel, binding to the specified ports. Parameters @@ -241,6 +242,9 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): req_port : int, optional The port to use for the REQ (raw input) channel. + hb_port : int, optional + The port to use for the hearbeat REP channel. + independent : bool, optional (default False) If set, the kernel process is guaranteed to survive if this process dies. If not set, an effort is made to ensure that the kernel is killed @@ -254,7 +258,8 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): where kernel_process is a Popen object and the ports are integers. """ return base_launch_kernel('from IPython.zmq.pykernel import main; main()', - xrep_port, pub_port, req_port, independent) + xrep_port, pub_port, req_port, hb_port, + independent) main = make_default_main(Kernel)