Show More
@@ -206,7 +206,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
206 | 206 | return True |
|
207 | 207 | elif key == QtCore.Qt.Key_Period: |
|
208 | 208 | message = 'Are you sure you want to restart the kernel?' |
|
209 | self.restart_kernel(message) | |
|
209 | self.restart_kernel(message, instant_death=False) | |
|
210 | 210 | return True |
|
211 | 211 | return super(FrontendWidget, self)._event_filter_console_keypress(event) |
|
212 | 212 | |
@@ -279,7 +279,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
279 | 279 | if self.custom_restart: |
|
280 | 280 | self.custom_restart_kernel_died.emit(since_last_heartbeat) |
|
281 | 281 | else: |
|
282 | self.restart_kernel(message) | |
|
282 | self.restart_kernel(message, instant_death=True) | |
|
283 | 283 | |
|
284 | 284 | def _handle_object_info_reply(self, rep): |
|
285 | 285 | """ Handle replies for call tips. |
@@ -341,9 +341,16 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
341 | 341 | self._append_plain_text('Kernel process is either remote or ' |
|
342 | 342 | 'unspecified. Cannot interrupt.\n') |
|
343 | 343 | |
|
344 | def restart_kernel(self, message): | |
|
344 | def restart_kernel(self, message, instant_death=False): | |
|
345 | 345 | """ Attempts to restart the running kernel. |
|
346 | 346 | """ |
|
347 | # FIXME: instant_death should be configurable via a checkbox in the | |
|
348 | # dialog. Right now at least the heartbeat path sets it to True and | |
|
349 | # the manual restart to False. But those should just be the | |
|
350 | # pre-selected states of a checkbox that the user could override if so | |
|
351 | # desired. But I don't know enough Qt to go implementing the checkbox | |
|
352 | # now. | |
|
353 | ||
|
347 | 354 | # We want to make sure that if this dialog is already happening, that |
|
348 | 355 | # other signals don't trigger it again. This can happen when the |
|
349 | 356 | # kernel_died heartbeat signal is emitted and the user is slow to |
@@ -360,7 +367,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
360 | 367 | message, buttons) |
|
361 | 368 | if result == QtGui.QMessageBox.Yes: |
|
362 | 369 | try: |
|
363 |
self.kernel_manager.restart_kernel( |
|
|
370 | self.kernel_manager.restart_kernel( | |
|
371 | instant_death=instant_death) | |
|
364 | 372 | except RuntimeError: |
|
365 | 373 | message = 'Kernel started externally. Cannot restart.\n' |
|
366 | 374 | self._append_plain_text(message) |
@@ -17,6 +17,7 b' from __future__ import print_function' | |||
|
17 | 17 | |
|
18 | 18 | # Standard library imports. |
|
19 | 19 | import __builtin__ |
|
20 | import atexit | |
|
20 | 21 | import sys |
|
21 | 22 | import time |
|
22 | 23 | import traceback |
@@ -30,13 +31,12 b' from IPython.utils import io' | |||
|
30 | 31 | from IPython.utils.jsonutil import json_clean |
|
31 | 32 | from IPython.lib import pylabtools |
|
32 | 33 | from IPython.utils.traitlets import Instance, Float |
|
33 |
from entry_point import base_launch_kernel, make_argument_parser, make_kernel, |
|
|
34 | start_kernel | |
|
34 | from entry_point import (base_launch_kernel, make_argument_parser, make_kernel, | |
|
35 | start_kernel) | |
|
35 | 36 | from iostream import OutStream |
|
36 | 37 | from session import Session, Message |
|
37 | 38 | from zmqshell import ZMQInteractiveShell |
|
38 | 39 | |
|
39 | ||
|
40 | 40 | #----------------------------------------------------------------------------- |
|
41 | 41 | # Main kernel class |
|
42 | 42 | #----------------------------------------------------------------------------- |
@@ -69,9 +69,20 b' class Kernel(Configurable):' | |||
|
69 | 69 | # adapt to milliseconds. |
|
70 | 70 | _poll_interval = Float(0.05, config=True) |
|
71 | 71 | |
|
72 | # If the shutdown was requested over the network, we leave here the | |
|
73 | # necessary reply message so it can be sent by our registered atexit | |
|
74 | # handler. This ensures that the reply is only sent to clients truly at | |
|
75 | # the end of our shutdown process (which happens after the underlying | |
|
76 | # IPython shell's own shutdown). | |
|
77 | _shutdown_message = None | |
|
78 | ||
|
72 | 79 | def __init__(self, **kwargs): |
|
73 | 80 | super(Kernel, self).__init__(**kwargs) |
|
74 | 81 | |
|
82 | # Before we even start up the shell, register *first* our exit handlers | |
|
83 | # so they come before the shell's | |
|
84 | atexit.register(self._at_shutdown) | |
|
85 | ||
|
75 | 86 | # Initialize the InteractiveShell subclass |
|
76 | 87 | self.shell = ZMQInteractiveShell.instance() |
|
77 | 88 | self.shell.displayhook.session = self.session |
@@ -82,7 +93,8 b' class Kernel(Configurable):' | |||
|
82 | 93 | |
|
83 | 94 | # Build dict of handlers for message types |
|
84 | 95 | msg_types = [ 'execute_request', 'complete_request', |
|
85 |
'object_info_request', 'history_request' |
|
|
96 | 'object_info_request', 'history_request', | |
|
97 | 'shutdown_request'] | |
|
86 | 98 | self.handlers = {} |
|
87 | 99 | for msg_type in msg_types: |
|
88 | 100 | self.handlers[msg_type] = getattr(self, msg_type) |
@@ -271,6 +283,11 b' class Kernel(Configurable):' | |||
|
271 | 283 | content, parent, ident) |
|
272 | 284 | io.raw_print(msg) |
|
273 | 285 | |
|
286 | def shutdown_request(self, ident, parent): | |
|
287 | self.shell.exit_now = True | |
|
288 | self._shutdown_message = self.session.msg(u'shutdown_reply', {}, parent) | |
|
289 | sys.exit(0) | |
|
290 | ||
|
274 | 291 | #--------------------------------------------------------------------------- |
|
275 | 292 | # Protected interface |
|
276 | 293 | #--------------------------------------------------------------------------- |
@@ -360,6 +377,17 b' class Kernel(Configurable):' | |||
|
360 | 377 | |
|
361 | 378 | return symbol, [] |
|
362 | 379 | |
|
380 | def _at_shutdown(self): | |
|
381 | """Actions taken at shutdown by the kernel, called by python's atexit. | |
|
382 | """ | |
|
383 | # io.rprint("Kernel at_shutdown") # dbg | |
|
384 | if self._shutdown_message is not None: | |
|
385 | self.reply_socket.send_json(self._shutdown_message) | |
|
386 | io.raw_print(self._shutdown_message) | |
|
387 | # A very short sleep to give zmq time to flush its message buffers | |
|
388 | # before Python truly shuts down. | |
|
389 | time.sleep(0.01) | |
|
390 | ||
|
363 | 391 | |
|
364 | 392 | class QtKernel(Kernel): |
|
365 | 393 | """A Kernel subclass with Qt support.""" |
@@ -367,10 +395,9 b' class QtKernel(Kernel):' | |||
|
367 | 395 | def start(self): |
|
368 | 396 | """Start a kernel with QtPy4 event loop integration.""" |
|
369 | 397 | |
|
370 |
from PyQt4 import |
|
|
371 |
from IPython.lib.guisupport import |
|
|
372 | get_app_qt4, start_event_loop_qt4 | |
|
373 | ) | |
|
398 | from PyQt4 import QtCore | |
|
399 | from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 | |
|
400 | ||
|
374 | 401 | self.app = get_app_qt4([" "]) |
|
375 | 402 | self.app.setQuitOnLastWindowClosed(False) |
|
376 | 403 | self.timer = QtCore.QTimer() |
@@ -388,6 +415,7 b' class WxKernel(Kernel):' | |||
|
388 | 415 | |
|
389 | 416 | import wx |
|
390 | 417 | from IPython.lib.guisupport import start_event_loop_wx |
|
418 | ||
|
391 | 419 | doi = self.do_one_iteration |
|
392 | 420 | # Wx uses milliseconds |
|
393 | 421 | poll_interval = int(1000*self._poll_interval) |
@@ -304,6 +304,23 b' class XReqSocketChannel(ZmqSocketChannel):' | |||
|
304 | 304 | self._queue_request(msg) |
|
305 | 305 | return msg['header']['msg_id'] |
|
306 | 306 | |
|
307 | def shutdown(self): | |
|
308 | """Request an immediate kernel shutdown. | |
|
309 | ||
|
310 | Upon receipt of the (empty) reply, client code can safely assume that | |
|
311 | the kernel has shut down and it's safe to forcefully terminate it if | |
|
312 | it's still alive. | |
|
313 | ||
|
314 | The kernel will send the reply via a function registered with Python's | |
|
315 | atexit module, ensuring it's truly done as the kernel is done with all | |
|
316 | normal operation. | |
|
317 | """ | |
|
318 | # Send quit message to kernel. Once we implement kernel-side setattr, | |
|
319 | # this should probably be done that way, but for now this will do. | |
|
320 | msg = self.session.msg('shutdown_request', {}) | |
|
321 | self._queue_request(msg) | |
|
322 | return msg['header']['msg_id'] | |
|
323 | ||
|
307 | 324 | def _handle_events(self, socket, events): |
|
308 | 325 | if events & POLLERR: |
|
309 | 326 | self._handle_err() |
@@ -700,14 +717,11 b' class KernelManager(HasTraits):' | |||
|
700 | 717 | """ Attempts to the stop the kernel process cleanly. If the kernel |
|
701 | 718 | cannot be stopped, it is killed, if possible. |
|
702 | 719 | """ |
|
703 | # Send quit message to kernel. Once we implement kernel-side setattr, | |
|
704 | # this should probably be done that way, but for now this will do. | |
|
705 | self.xreq_channel.execute('get_ipython().exit_now=True', silent=True) | |
|
706 | ||
|
720 | self.xreq_channel.shutdown() | |
|
707 | 721 | # Don't send any additional kernel kill messages immediately, to give |
|
708 | 722 | # the kernel a chance to properly execute shutdown actions. Wait for at |
|
709 |
# most |
|
|
710 |
for i in range( |
|
|
723 | # most 1s, checking every 0.1s. | |
|
724 | for i in range(10): | |
|
711 | 725 | if self.is_alive: |
|
712 | 726 | time.sleep(0.1) |
|
713 | 727 | else: |
@@ -717,17 +731,30 b' class KernelManager(HasTraits):' | |||
|
717 | 731 | if self.has_kernel: |
|
718 | 732 | self.kill_kernel() |
|
719 | 733 | |
|
720 | def restart_kernel(self): | |
|
734 | def restart_kernel(self, instant_death=False): | |
|
721 | 735 | """Restarts a kernel with the same arguments that were used to launch |
|
722 | 736 | it. If the old kernel was launched with random ports, the same ports |
|
723 | 737 | will be used for the new kernel. |
|
738 | ||
|
739 | Parameters | |
|
740 | ---------- | |
|
741 | instant_death : bool, optional | |
|
742 | If True, the kernel is forcefully restarted *immediately*, without | |
|
743 | having a chance to do any cleanup action. Otherwise the kernel is | |
|
744 | given 1s to clean up before a forceful restart is issued. | |
|
745 | ||
|
746 | In all cases the kernel is restarted, the only difference is whether | |
|
747 | it is given a chance to perform a clean shutdown or not. | |
|
724 | 748 | """ |
|
725 | 749 | if self._launch_args is None: |
|
726 | 750 | raise RuntimeError("Cannot restart the kernel. " |
|
727 | 751 | "No previous call to 'start_kernel'.") |
|
728 | 752 | else: |
|
729 | 753 | if self.has_kernel: |
|
754 | if instant_death: | |
|
730 | 755 | self.kill_kernel() |
|
756 | else: | |
|
757 | self.shutdown_kernel() | |
|
731 | 758 | self.start_kernel(**self._launch_args) |
|
732 | 759 | |
|
733 | 760 | @property |
@@ -755,6 +782,8 b' class KernelManager(HasTraits):' | |||
|
755 | 782 | @property |
|
756 | 783 | def is_alive(self): |
|
757 | 784 | """Is the kernel process still running?""" |
|
785 | # FIXME: not using a heartbeat means this method is broken for any | |
|
786 | # remote kernel, it's only capable of handling local kernels. | |
|
758 | 787 | if self.kernel is not None: |
|
759 | 788 | if self.kernel.poll() is None: |
|
760 | 789 | return True |
@@ -535,6 +535,48 b' Message type: ``history_reply``::' | |||
|
535 | 535 | 'history' : dict, |
|
536 | 536 | } |
|
537 | 537 | |
|
538 | ||
|
539 | Kernel shutdown | |
|
540 | --------------- | |
|
541 | ||
|
542 | The clients can request the kernel to shut itself down; this is used in | |
|
543 | multiple cases: | |
|
544 | ||
|
545 | - when the user chooses to close the client application via a menu or window | |
|
546 | control. | |
|
547 | - when the user types 'exit' or 'quit' (or their uppercase magic equivalents). | |
|
548 | - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the | |
|
549 | IPythonQt client) to force a kernel restart to get a clean kernel without | |
|
550 | losing client-side state like history or inlined figures. | |
|
551 | ||
|
552 | The client sends a shutdown request to the kernel, and once it receives the | |
|
553 | reply message (which is otherwise empty), it can assume that the kernel has | |
|
554 | completed shutdown safely. | |
|
555 | ||
|
556 | Upon their own shutdown, client applications will typically execute a last | |
|
557 | minute sanity check and forcefully terminate any kernel that is still alive, to | |
|
558 | avoid leaving stray processes in the user's machine. | |
|
559 | ||
|
560 | For both shutdown request and reply, there is no actual content that needs to | |
|
561 | be sent, so the content dict is empty. | |
|
562 | ||
|
563 | Message type: ``shutdown_request``:: | |
|
564 | ||
|
565 | content = { | |
|
566 | } | |
|
567 | ||
|
568 | Message type: ``shutdown_reply``:: | |
|
569 | ||
|
570 | content = { | |
|
571 | } | |
|
572 | ||
|
573 | .. Note:: | |
|
574 | ||
|
575 | When the clients detect a dead kernel thanks to inactivity on the heartbeat | |
|
576 | socket, they simply send a forceful process termination signal, since a dead | |
|
577 | process is unlikely to respond in any useful way to messages. | |
|
578 | ||
|
579 | ||
|
538 | 580 | Messages on the PUB/SUB socket |
|
539 | 581 | ============================== |
|
540 | 582 |
General Comments 0
You need to be logged in to leave comments.
Login now