##// END OF EJS Templates
Merge pull request #6305 from minrk/switch-kernel-close-ws...
Brian E. Granger -
r17695:f98e5939 merge
parent child Browse files
Show More
@@ -0,0 +1,43 b''
1
2 //
3 // Tests for the Session object
4 //
5
6 casper.notebook_test(function () {
7 this.evaluate(function () {
8 var kernel = IPython.notebook.session.kernel;
9 IPython._channels = [
10 kernel.shell_channel,
11 kernel.iopub_channel,
12 kernel.stdin_channel
13 ];
14 IPython.notebook.session.delete();
15 });
16
17 this.waitFor(function () {
18 return this.evaluate(function(){
19 for (var i=0; i < IPython._channels.length; i++) {
20 var ws = IPython._channels[i];
21 if (ws.readyState !== ws.CLOSED) {
22 return false;
23 }
24 }
25 return true;
26 });
27 });
28
29 this.then(function () {
30 var states = this.evaluate(function() {
31 var states = [];
32 for (var i = 0; i < IPython._channels.length; i++) {
33 states.push(IPython._channels[i].readyState);
34 }
35 return states;
36 });
37
38 for (var i = 0; i < states.length; i++) {
39 this.test.assertEquals(states[i], WebSocket.CLOSED,
40 "Session.delete closes websockets[" + i + "]");
41 }
42 });
43 });
@@ -84,6 +84,9 b' class KernelActionHandler(IPythonHandler):'
84
84
85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
86
86
87 def __repr__(self):
88 return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
89
87 def create_stream(self):
90 def create_stream(self):
88 km = self.kernel_manager
91 km = self.kernel_manager
89 meth = getattr(km, 'connect_%s' % self.channel)
92 meth = getattr(km, 'connect_%s' % self.channel)
@@ -145,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):'
145 self.zmq_stream.on_recv(self._on_zmq_reply)
148 self.zmq_stream.on_recv(self._on_zmq_reply)
146
149
147 def on_message(self, msg):
150 def on_message(self, msg):
151 if self.zmq_stream is None:
152 return
153 elif self.zmq_stream.closed():
154 self.log.info("%s closed, closing websocket.", self)
155 self.close()
156 return
148 msg = json.loads(msg)
157 msg = json.loads(msg)
149 self.session.send(self.zmq_stream, msg)
158 self.session.send(self.zmq_stream, msg)
150
159
@@ -54,9 +54,19 b' define(['
54 return;
54 return;
55 }
55 }
56 var ks = this.kernelspecs[kernel_name];
56 var ks = this.kernelspecs[kernel_name];
57 this.events.trigger('spec_changed.Kernel', ks);
57 try {
58 this.notebook.session.delete();
59 this.notebook.start_session(kernel_name);
58 this.notebook.start_session(kernel_name);
59 } catch (e) {
60 if (e.name === 'SessionAlreadyStarting') {
61 console.log("Cannot change kernel while waiting for pending session start.");
62 } else {
63 // unhandled error
64 throw e;
65 }
66 // only trigger spec_changed if change was successful
67 return;
68 }
69 this.events.trigger('spec_changed.Kernel', ks);
60 };
70 };
61
71
62 KernelSelector.prototype.bind_events = function() {
72 KernelSelector.prototype.bind_events = function() {
@@ -157,12 +157,13 b' define(['
157 }
157 }
158 });
158 });
159 this.element.find('#kill_and_exit').click(function () {
159 this.element.find('#kill_and_exit').click(function () {
160 that.notebook.session.delete();
160 var close_window = function () {
161 setTimeout(function(){
162 // allow closing of new tabs in Chromium, impossible in FF
161 // allow closing of new tabs in Chromium, impossible in FF
163 window.open('', '_self', '');
162 window.open('', '_self', '');
164 window.close();
163 window.close();
165 }, 500);
164 };
165 // finish with close on success or failure
166 that.notebook.session.delete(close_window, close_window);
166 });
167 });
167 // Edit
168 // Edit
168 this.element.find('#cut_cell').click(function () {
169 this.element.find('#cut_cell').click(function () {
@@ -62,6 +62,7 b' define(['
62 this.save_widget = options.save_widget;
62 this.save_widget = options.save_widget;
63 this.tooltip = new tooltip.Tooltip(this.events);
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.ws_url = options.ws_url;
64 this.ws_url = options.ws_url;
65 this._session_starting = false;
65 // default_kernel_name is a temporary measure while we implement proper
66 // default_kernel_name is a temporary measure while we implement proper
66 // kernel selection and delayed start. Do not rely on it.
67 // kernel selection and delayed start. Do not rely on it.
67 this.default_kernel_name = 'python';
68 this.default_kernel_name = 'python';
@@ -1525,9 +1526,38 b' define(['
1525 * @method start_session
1526 * @method start_session
1526 */
1527 */
1527 Notebook.prototype.start_session = function (kernel_name) {
1528 Notebook.prototype.start_session = function (kernel_name) {
1529 var that = this;
1528 if (kernel_name === undefined) {
1530 if (kernel_name === undefined) {
1529 kernel_name = this.default_kernel_name;
1531 kernel_name = this.default_kernel_name;
1530 }
1532 }
1533 if (this._session_starting) {
1534 throw new session.SessionAlreadyStarting();
1535 }
1536 this._session_starting = true;
1537
1538 if (this.session !== null) {
1539 var s = this.session;
1540 this.session = null;
1541 // need to start the new session in a callback after delete,
1542 // because javascript does not guarantee the ordering of AJAX requests (?!)
1543 s.delete(function () {
1544 // on successful delete, start new session
1545 that._session_starting = false;
1546 that.start_session(kernel_name);
1547 }, function (jqXHR, status, error) {
1548 // log the failed delete, but still create a new session
1549 // 404 just means it was already deleted by someone else,
1550 // but other errors are possible.
1551 utils.log_ajax_error(jqXHR, status, error);
1552 that._session_starting = false;
1553 that.start_session(kernel_name);
1554 }
1555 );
1556 return;
1557 }
1558
1559
1560
1531 this.session = new session.Session({
1561 this.session = new session.Session({
1532 base_url: this.base_url,
1562 base_url: this.base_url,
1533 ws_url: this.ws_url,
1563 ws_url: this.ws_url,
@@ -1539,7 +1569,10 b' define(['
1539 kernel_name: kernel_name,
1569 kernel_name: kernel_name,
1540 notebook: this});
1570 notebook: this});
1541
1571
1542 this.session.start($.proxy(this._session_started, this));
1572 this.session.start(
1573 $.proxy(this._session_started, this),
1574 $.proxy(this._session_start_failed, this)
1575 );
1543 };
1576 };
1544
1577
1545
1578
@@ -1549,6 +1582,7 b' define(['
1549 *
1582 *
1550 */
1583 */
1551 Notebook.prototype._session_started = function(){
1584 Notebook.prototype._session_started = function (){
1585 this._session_starting = false;
1552 this.kernel = this.session.kernel;
1586 this.kernel = this.session.kernel;
1553 var ncells = this.ncells();
1587 var ncells = this.ncells();
1554 for (var i=0; i<ncells; i++) {
1588 for (var i=0; i<ncells; i++) {
@@ -1558,6 +1592,10 b' define(['
1558 }
1592 }
1559 }
1593 }
1560 };
1594 };
1595 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1596 this._session_starting = false;
1597 utils.log_ajax_error(jqxhr, status, error);
1598 };
1561
1599
1562 /**
1600 /**
1563 * Prompt the user to restart the IPython kernel.
1601 * Prompt the user to restart the IPython kernel.
@@ -109,6 +109,12 b' define(['
109 knw.set_message("Restarting kernel", 2000);
109 knw.set_message("Restarting kernel", 2000);
110 });
110 });
111
111
112 this.events.on('status_dead.Kernel',function () {
113 that.save_widget.update_document_title();
114 knw.danger("Dead kernel");
115 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
116 });
117
112 this.events.on('status_interrupting.Kernel',function () {
118 this.events.on('status_interrupting.Kernel',function () {
113 knw.set_message("Interrupting kernel", 2000);
119 knw.set_message("Interrupting kernel", 2000);
114 });
120 });
@@ -118,6 +124,8 b' define(['
118 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
124 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
119
125
120 this.events.on('status_started.Kernel', function (evt, data) {
126 this.events.on('status_started.Kernel', function (evt, data) {
127 knw.info("Websockets Connected", 500);
128 that.events.trigger('status_busy.Kernel');
121 data.kernel.kernel_info(function () {
129 data.kernel.kernel_info(function () {
122 that.events.trigger('status_idle.Kernel');
130 that.events.trigger('status_idle.Kernel');
123 });
131 });
@@ -153,8 +161,13 b' define(['
153 var ws_url = data.ws_url;
161 var ws_url = data.ws_url;
154 var early = data.early;
162 var early = data.early;
155 var msg;
163 var msg;
164
165 $kernel_ind_icon
166 .attr('class', 'kernel_disconnected_icon')
167 .attr('title', 'No Connection to Kernel');
168
156 if (!early) {
169 if (!early) {
157 knw.set_message('Reconnecting WebSockets', 1000);
170 knw.warning('Reconnecting');
158 setTimeout(function () {
171 setTimeout(function () {
159 kernel.start_channels();
172 kernel.start_channels();
160 }, 5000);
173 }, 5000);
@@ -173,7 +186,7 b' define(['
173 "OK": {},
186 "OK": {},
174 "Reconnect": {
187 "Reconnect": {
175 click: function () {
188 click: function () {
176 knw.set_message('Reconnecting WebSockets', 1000);
189 knw.warning('Reconnecting');
177 setTimeout(function () {
190 setTimeout(function () {
178 kernel.start_channels();
191 kernel.start_channels();
179 }, 5000);
192 }, 5000);
@@ -43,4 +43,12 b''
43 .icon(@fa-var-circle);
43 .icon(@fa-var-circle);
44 }
44 }
45
45
46 .kernel_dead_icon:before {
47 .icon(@fa-var-bomb);
48 }
49
50 .kernel_disconnected_icon:before {
51 .icon(@fa-var-chain-broken);
52 }
53
46
54
@@ -179,10 +179,18 b' define(['
179 that._websocket_closed(ws_host_url, false);
179 that._websocket_closed(ws_host_url, false);
180 }
180 }
181 };
181 };
182 var ws_error = function(evt){
183 if (already_called_onclose){
184 return;
185 }
186 already_called_onclose = true;
187 that._websocket_closed(ws_host_url, false);
188 };
182 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
189 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
183 for (var i=0; i < channels.length; i++) {
190 for (var i=0; i < channels.length; i++) {
184 channels[i].onopen = $.proxy(this._ws_opened, this);
191 channels[i].onopen = $.proxy(this._ws_opened, this);
185 channels[i].onclose = ws_closed_early;
192 channels[i].onclose = ws_closed_early;
193 channels[i].onerror = ws_error;
186 }
194 }
187 // switch from early-close to late-close message after 1s
195 // switch from early-close to late-close message after 1s
188 setTimeout(function() {
196 setTimeout(function() {
@@ -211,7 +219,7 b' define(['
211 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
219 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
212 for (var i=0; i < channels.length; i++) {
220 for (var i=0; i < channels.length; i++) {
213 // if any channel is not ready, don't trigger event.
221 // if any channel is not ready, don't trigger event.
214 if ( !channels[i].readyState ) return;
222 if ( channels[i].readyState == WebSocket.OPEN ) return;
215 }
223 }
216 // all events ready, trigger started event.
224 // all events ready, trigger started event.
217 this.events.trigger('status_started.Kernel', {kernel: this});
225 this.events.trigger('status_started.Kernel', {kernel: this});
@@ -385,15 +393,17 b' define(['
385 };
393 };
386
394
387
395
388 Kernel.prototype.kill = function () {
396 Kernel.prototype.kill = function (success, error) {
389 if (this.running) {
397 if (this.running) {
390 this.running = false;
398 this.running = false;
391 var settings = {
399 var settings = {
392 cache : false,
400 cache : false,
393 type : "DELETE",
401 type : "DELETE",
394 error : utils.log_ajax_error,
402 success : success,
403 error : error || utils.log_ajax_error,
395 };
404 };
396 $.ajax(utils.url_join_encode(this.kernel_url), settings);
405 $.ajax(utils.url_join_encode(this.kernel_url), settings);
406 this.stop_channels();
397 }
407 }
398 };
408 };
399
409
@@ -21,7 +21,7 b' define(['
21 this.ws_url = options.ws_url;
21 this.ws_url = options.ws_url;
22 };
22 };
23
23
24 Session.prototype.start = function(callback) {
24 Session.prototype.start = function (success, error) {
25 var that = this;
25 var that = this;
26 var model = {
26 var model = {
27 notebook : {
27 notebook : {
@@ -40,11 +40,17 b' define(['
40 dataType : "json",
40 dataType : "json",
41 success : function (data, status, xhr) {
41 success : function (data, status, xhr) {
42 that._handle_start_success(data);
42 that._handle_start_success(data);
43 if (callback) {
43 if (success) {
44 callback(data, status, xhr);
44 success(data, status, xhr);
45 }
45 }
46 },
46 },
47 error : utils.log_ajax_error,
47 error : function (xhr, status, err) {
48 that._handle_start_failure(xhr, status, err);
49 if (error !== undefined) {
50 error(xhr, status, err);
51 }
52 utils.log_ajax_error(xhr, status, err);
53 }
48 };
54 };
49 var url = utils.url_join_encode(this.base_url, 'api/sessions');
55 var url = utils.url_join_encode(this.base_url, 'api/sessions');
50 $.ajax(url, settings);
56 $.ajax(url, settings);
@@ -71,15 +77,19 b' define(['
71 $.ajax(url, settings);
77 $.ajax(url, settings);
72 };
78 };
73
79
74 Session.prototype.delete = function() {
80 Session.prototype.delete = function (success, error) {
75 var settings = {
81 var settings = {
76 processData : false,
82 processData : false,
77 cache : false,
83 cache : false,
78 type : "DELETE",
84 type : "DELETE",
79 dataType : "json",
85 dataType : "json",
80 error : utils.log_ajax_error,
86 success : success,
87 error : error || utils.log_ajax_error,
81 };
88 };
89 if (this.kernel) {
82 this.kernel.running = false;
90 this.kernel.running = false;
91 this.kernel.stop_channels();
92 }
83 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
93 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
84 $.ajax(url, settings);
94 $.ajax(url, settings);
85 };
95 };
@@ -100,6 +110,11 b' define(['
100 this.kernel._kernel_started(data.kernel);
110 this.kernel._kernel_started(data.kernel);
101 };
111 };
102
112
113 Session.prototype._handle_start_failure = function (xhr, status, error) {
114 this.events.trigger('start_failed.Session', [this, xhr, status, error]);
115 this.events.trigger('status_dead.Kernel');
116 };
117
103 /**
118 /**
104 * Prompt the user to restart the IPython kernel.
119 * Prompt the user to restart the IPython kernel.
105 *
120 *
@@ -118,8 +133,18 b' define(['
118 this.kernel.kill();
133 this.kernel.kill();
119 };
134 };
120
135
136 var SessionAlreadyStarting = function (message) {
137 this.name = "SessionAlreadyStarting";
138 this.message = (message || "");
139 };
140
141 SessionAlreadyStarting.prototype = Error.prototype;
142
121 // For backwards compatability.
143 // For backwards compatability.
122 IPython.Session = Session;
144 IPython.Session = Session;
123
145
124 return {'Session': Session};
146 return {
147 Session: Session,
148 SessionAlreadyStarting: SessionAlreadyStarting,
149 };
125 });
150 });
@@ -9998,6 +9998,38 b' ul#help_menu li a i {'
9998 .kernel_busy_icon:before.pull-right {
9998 .kernel_busy_icon:before.pull-right {
9999 margin-left: .3em;
9999 margin-left: .3em;
10000 }
10000 }
10001 .kernel_dead_icon:before {
10002 display: inline-block;
10003 font-family: FontAwesome;
10004 font-style: normal;
10005 font-weight: normal;
10006 line-height: 1;
10007 -webkit-font-smoothing: antialiased;
10008 -moz-osx-font-smoothing: grayscale;
10009 content: "\f1e2";
10010 }
10011 .kernel_dead_icon:before.pull-left {
10012 margin-right: .3em;
10013 }
10014 .kernel_dead_icon:before.pull-right {
10015 margin-left: .3em;
10016 }
10017 .kernel_disconnected_icon:before {
10018 display: inline-block;
10019 font-family: FontAwesome;
10020 font-style: normal;
10021 font-weight: normal;
10022 line-height: 1;
10023 -webkit-font-smoothing: antialiased;
10024 -moz-osx-font-smoothing: grayscale;
10025 content: "\f127";
10026 }
10027 .kernel_disconnected_icon:before.pull-left {
10028 margin-right: .3em;
10029 }
10030 .kernel_disconnected_icon:before.pull-right {
10031 margin-left: .3em;
10032 }
10001 .notification_widget {
10033 .notification_widget {
10002 color: #777777;
10034 color: #777777;
10003 padding: 1px 12px;
10035 padding: 1px 12px;
@@ -1,6 +1,6 b''
1
1
2 //
2 //
3 // Miscellaneous javascript tests
3 // Kernel tests
4 //
4 //
5 casper.notebook_test(function () {
5 casper.notebook_test(function () {
6 this.evaluate(function () {
6 this.evaluate(function () {
@@ -25,4 +25,40 b' casper.notebook_test(function () {'
25 this.test.assertTrue( kernel_info_response.content !== undefined, 'Kernel_info_reply is not undefined');
25 this.test.assertTrue( kernel_info_response.content !== undefined, 'Kernel_info_reply is not undefined');
26 });
26 });
27
27
28 this.thenEvaluate(function () {
29 var kernel = IPython.notebook.session.kernel;
30 IPython._channels = [
31 kernel.shell_channel,
32 kernel.iopub_channel,
33 kernel.stdin_channel
34 ];
35 kernel.kill();
36 });
37
38 this.waitFor(function () {
39 return this.evaluate(function(){
40 for (var i=0; i < IPython._channels.length; i++) {
41 var ws = IPython._channels[i];
42 if (ws.readyState !== ws.CLOSED) {
43 return false;
44 }
45 }
46 return true;
47 });
48 });
49
50 this.then(function () {
51 var states = this.evaluate(function() {
52 var states = [];
53 for (var i = 0; i < IPython._channels.length; i++) {
54 states.push(IPython._channels[i].readyState);
55 }
56 return states;
57 });
58
59 for (var i = 0; i < states.length; i++) {
60 this.test.assertEquals(states[i], WebSocket.CLOSED,
61 "Kernel.kill closes websockets[" + i + "]");
62 }
63 });
28 });
64 });
General Comments 0
You need to be logged in to leave comments. Login now