diff --git a/IPython/html/static/notebook/js/notificationarea.js b/IPython/html/static/notebook/js/notificationarea.js index e3b1e9e..4608436 100644 --- a/IPython/html/static/notebook/js/notificationarea.js +++ b/IPython/html/static/notebook/js/notificationarea.js @@ -141,18 +141,24 @@ define([ knw.set_message("Restarting kernel", 2000); }); - this.events.on('status_autorestarting.Kernel', function () { - dialog.modal({ - notebook: that.notebook, - keyboard_manager: that.keyboard_manager, - title: "Kernel Restarting", - body: "The kernel appears to have died. It will restart automatically.", - buttons: { - OK : { - class : "btn-primary" + this.events.on('status_autorestarting.Kernel', function (evt, info) { + // only show the dialog on the first restart attempt + if (info.attempt == 1) { + // hide existing modal dialog + $(".modal").modal('hide'); + + dialog.modal({ + notebook: that.notebook, + keyboard_manager: that.keyboard_manager, + title: "Kernel Restarting", + body: "The kernel appears to have died. It will restart automatically.", + buttons: { + OK : { + class : "btn-primary" + } } - } - }); + }); + }; that.save_widget.update_document_title(); knw.danger("Dead kernel"); @@ -174,9 +180,13 @@ define([ }); this.events.on('connection_failed.Kernel', function () { + // hide existing dialog + $(".modal").modal('hide'); + var msg = "A WebSocket connection could not be established." + " You will NOT be able to run code. Check your" + " network connection or notebook server configuration."; + dialog.modal({ title: "WebSocket connection failed", body: msg, @@ -193,34 +203,48 @@ define([ }); }); - this.events.on('kernel_dead.Kernel status_killed.Kernel status_killed.Session', function () { + this.events.on('status_killed.Kernel status_killed.Session', function () { that.save_widget.update_document_title(); knw.danger("Dead kernel"); $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); }); this.events.on('kernel_dead.Kernel', function () { - var msg = 'The kernel has died, and the automatic restart has failed.' + - ' It is possible the kernel cannot be restarted.' + - ' If you are not able to restart the kernel, you will still be able to save' + - ' the notebook, but running code will no longer work until the notebook' + - ' is reopened.'; - dialog.modal({ - title: "Dead kernel", - body : msg, - keyboard_manager: that.keyboard_manager, - notebook: that.notebook, - buttons : { - "Manual Restart": { - class: "btn-danger", - click: function () { - that.notebook.start_session(); - } - }, + var showMsg = function () { + // hide existing dialog + $(".modal").modal('hide'); + + var msg = 'The kernel has died, and the automatic restart has failed.' + + ' It is possible the kernel cannot be restarted.' + + ' If you are not able to restart the kernel, you will still be able to save' + + ' the notebook, but running code will no longer work until the notebook' + + ' is reopened.'; + + dialog.modal({ + title: "Dead kernel", + body : msg, + keyboard_manager: that.keyboard_manager, + notebook: that.notebook, + buttons : { + "Manual Restart": { + class: "btn-danger", + click: function () { + that.notebook.start_session(); + } + }, "Don't restart": {} - } - }); + } + }); + + return false; + }; + + that.save_widget.update_document_title(); + knw.danger("Dead kernel", undefined, showMsg); + $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); + + showMsg(); }); this.events.on('kernel_dead.Session', function (evt, info) { @@ -246,6 +270,9 @@ define([ cm_open = $.proxy(cm.refresh, cm); } + // hide existing modal dialog + $(".modal").modal('hide'); + dialog.modal({ title: "Failed to start the kernel", body : msg, diff --git a/IPython/html/static/services/kernels/js/kernel.js b/IPython/html/static/services/kernels/js/kernel.js index 7aaadf0..4e60d09 100644 --- a/IPython/html/static/services/kernels/js/kernel.js +++ b/IPython/html/static/services/kernels/js/kernel.js @@ -61,6 +61,8 @@ define([ this.last_msg_id = null; this.last_msg_callbacks = {}; + + this._autorestart_attempt = 0; }; /** @@ -110,6 +112,10 @@ define([ this.events.on('status_ready.Kernel', record_status); this.events.on('status_killed.Kernel', record_status); this.events.on('kernel_dead.Kernel', record_status); + + this.events.on('status_ready.Kernel', function () { + that._autorestart_attempt = 0; + }); }; /** @@ -922,8 +928,9 @@ define([ // in that it means the kernel died and the server is restarting it. // status_restarting sets the notification widget, // autorestart shows the more prominent dialog. + this._autorestart_attempt = this._autorestart_attempt + 1; this.events.trigger('status_restarting.Kernel', {kernel: this}); - this.events.trigger('status_autorestarting.Kernel', {kernel: this}); + this.events.trigger('status_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt}); } else if (execution_state === 'dead') { this.events.trigger('kernel_dead.Kernel', {kernel: this}); diff --git a/IPython/html/tests/services/kernel.js b/IPython/html/tests/services/kernel.js index 48e889e..bba1c28 100644 --- a/IPython/html/tests/services/kernel.js +++ b/IPython/html/tests/services/kernel.js @@ -217,12 +217,6 @@ casper.notebook_test(function () { // wait for any last idle/busy messages to be handled this.wait_for_kernel_ready(); - // TODO: test for failed restart, that it triggers - // kernel_dead.Kernel? How to do this? - - // TODO: test for status_autorestarting.Kernel? how to trigger - // this? - // check for events in the interrupt cycle this.event_test( 'interrupt', @@ -271,4 +265,53 @@ casper.notebook_test(function () { }); } ); + + // start the kernel back up + this.thenEvaluate(function () { + IPython.notebook.kernel.restart(); + }); + this.waitFor(this.kernel_running); + this.wait_for_kernel_ready(); + + // test handling of autorestarting messages + this.event_test( + 'autorestarting', + [ + 'status_restarting.Kernel', + 'status_autorestarting.Kernel', + ], + function () { + this.thenEvaluate(function () { + var cell = IPython.notebook.get_cell(0); + cell.set_text('import os\n' + 'os._exit(1)'); + cell.execute(); + }); + } + ); + this.wait_for_kernel_ready(); + + // test handling of failed restart + this.event_test( + 'failed_restart', + [ + 'status_restarting.Kernel', + 'status_autorestarting.Kernel', + 'kernel_dead.Kernel' + ], + function () { + this.thenEvaluate(function () { + var cell = IPython.notebook.get_cell(0); + cell.set_text("import os\n" + + "from IPython.kernel.connect import get_connection_file\n" + + "with open(get_connection_file(), 'w') as f:\n" + + " f.write('garbage')\n" + + "os._exit(1)"); + cell.execute(); + }); + }, + + // need an extra-long timeout, because it needs to try + // restarting the kernel 5 times! + 20000 + ); }); diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index b866122..0a99537 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -584,7 +584,7 @@ casper.dashboard_test = function (test) { // note that this will only work for UNIQUE events -- if you want to // listen for the same event twice, this will not work! -casper.event_test = function (name, events, action) { +casper.event_test = function (name, events, action, timeout) { // set up handlers to listen for each of the events this.thenEvaluate(function (events) { @@ -611,7 +611,7 @@ casper.event_test = function (name, events, action) { return this.evaluate(function (events) { return IPython._events_triggered.length >= events.length; }, [events]); - }); + }, undefined, undefined, timeout); // test that the events were triggered in the proper order this.then(function () {