##// 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 85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
86 86
87 def __repr__(self):
88 return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
89
87 90 def create_stream(self):
88 91 km = self.kernel_manager
89 92 meth = getattr(km, 'connect_%s' % self.channel)
@@ -145,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):'
145 148 self.zmq_stream.on_recv(self._on_zmq_reply)
146 149
147 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 157 msg = json.loads(msg)
149 158 self.session.send(self.zmq_stream, msg)
150 159
@@ -54,9 +54,19 b' define(['
54 54 return;
55 55 }
56 56 var ks = this.kernelspecs[kernel_name];
57 try {
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 }
57 69 this.events.trigger('spec_changed.Kernel', ks);
58 this.notebook.session.delete();
59 this.notebook.start_session(kernel_name);
60 70 };
61 71
62 72 KernelSelector.prototype.bind_events = function() {
@@ -157,12 +157,13 b' define(['
157 157 }
158 158 });
159 159 this.element.find('#kill_and_exit').click(function () {
160 that.notebook.session.delete();
161 setTimeout(function(){
160 var close_window = function () {
162 161 // allow closing of new tabs in Chromium, impossible in FF
163 162 window.open('', '_self', '');
164 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 168 // Edit
168 169 this.element.find('#cut_cell').click(function () {
@@ -62,6 +62,7 b' define(['
62 62 this.save_widget = options.save_widget;
63 63 this.tooltip = new tooltip.Tooltip(this.events);
64 64 this.ws_url = options.ws_url;
65 this._session_starting = false;
65 66 // default_kernel_name is a temporary measure while we implement proper
66 67 // kernel selection and delayed start. Do not rely on it.
67 68 this.default_kernel_name = 'python';
@@ -1525,9 +1526,38 b' define(['
1525 1526 * @method start_session
1526 1527 */
1527 1528 Notebook.prototype.start_session = function (kernel_name) {
1529 var that = this;
1528 1530 if (kernel_name === undefined) {
1529 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 1561 this.session = new session.Session({
1532 1562 base_url: this.base_url,
1533 1563 ws_url: this.ws_url,
@@ -1539,7 +1569,10 b' define(['
1539 1569 kernel_name: kernel_name,
1540 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
@@ -1548,7 +1581,8 b' define(['
1548 1581 * comm manager to the widget manager
1549 1582 *
1550 1583 */
1551 Notebook.prototype._session_started = function(){
1584 Notebook.prototype._session_started = function (){
1585 this._session_starting = false;
1552 1586 this.kernel = this.session.kernel;
1553 1587 var ncells = this.ncells();
1554 1588 for (var i=0; i<ncells; i++) {
@@ -1558,7 +1592,11 b' define(['
1558 1592 }
1559 1593 }
1560 1594 };
1561
1595 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1596 this._session_starting = false;
1597 utils.log_ajax_error(jqxhr, status, error);
1598 };
1599
1562 1600 /**
1563 1601 * Prompt the user to restart the IPython kernel.
1564 1602 *
@@ -109,6 +109,12 b' define(['
109 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 118 this.events.on('status_interrupting.Kernel',function () {
113 119 knw.set_message("Interrupting kernel", 2000);
114 120 });
@@ -118,6 +124,8 b' define(['
118 124 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
119 125
120 126 this.events.on('status_started.Kernel', function (evt, data) {
127 knw.info("Websockets Connected", 500);
128 that.events.trigger('status_busy.Kernel');
121 129 data.kernel.kernel_info(function () {
122 130 that.events.trigger('status_idle.Kernel');
123 131 });
@@ -153,8 +161,13 b' define(['
153 161 var ws_url = data.ws_url;
154 162 var early = data.early;
155 163 var msg;
164
165 $kernel_ind_icon
166 .attr('class', 'kernel_disconnected_icon')
167 .attr('title', 'No Connection to Kernel');
168
156 169 if (!early) {
157 knw.set_message('Reconnecting WebSockets', 1000);
170 knw.warning('Reconnecting');
158 171 setTimeout(function () {
159 172 kernel.start_channels();
160 173 }, 5000);
@@ -173,7 +186,7 b' define(['
173 186 "OK": {},
174 187 "Reconnect": {
175 188 click: function () {
176 knw.set_message('Reconnecting WebSockets', 1000);
189 knw.warning('Reconnecting');
177 190 setTimeout(function () {
178 191 kernel.start_channels();
179 192 }, 5000);
@@ -43,4 +43,12 b''
43 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 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 189 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
183 190 for (var i=0; i < channels.length; i++) {
184 191 channels[i].onopen = $.proxy(this._ws_opened, this);
185 192 channels[i].onclose = ws_closed_early;
193 channels[i].onerror = ws_error;
186 194 }
187 195 // switch from early-close to late-close message after 1s
188 196 setTimeout(function() {
@@ -211,7 +219,7 b' define(['
211 219 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
212 220 for (var i=0; i < channels.length; i++) {
213 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 224 // all events ready, trigger started event.
217 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 397 if (this.running) {
390 398 this.running = false;
391 399 var settings = {
392 400 cache : false,
393 401 type : "DELETE",
394 error : utils.log_ajax_error,
402 success : success,
403 error : error || utils.log_ajax_error,
395 404 };
396 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 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 25 var that = this;
26 26 var model = {
27 27 notebook : {
@@ -40,11 +40,17 b' define(['
40 40 dataType : "json",
41 41 success : function (data, status, xhr) {
42 42 that._handle_start_success(data);
43 if (callback) {
44 callback(data, status, xhr);
43 if (success) {
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 55 var url = utils.url_join_encode(this.base_url, 'api/sessions');
50 56 $.ajax(url, settings);
@@ -71,15 +77,19 b' define(['
71 77 $.ajax(url, settings);
72 78 };
73 79
74 Session.prototype.delete = function() {
80 Session.prototype.delete = function (success, error) {
75 81 var settings = {
76 82 processData : false,
77 83 cache : false,
78 84 type : "DELETE",
79 85 dataType : "json",
80 error : utils.log_ajax_error,
86 success : success,
87 error : error || utils.log_ajax_error,
81 88 };
82 this.kernel.running = false;
89 if (this.kernel) {
90 this.kernel.running = false;
91 this.kernel.stop_channels();
92 }
83 93 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
84 94 $.ajax(url, settings);
85 95 };
@@ -99,6 +109,11 b' define(['
99 109 this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
100 110 this.kernel._kernel_started(data.kernel);
101 111 };
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 };
102 117
103 118 /**
104 119 * Prompt the user to restart the IPython kernel.
@@ -118,8 +133,18 b' define(['
118 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 143 // For backwards compatability.
122 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 9998 .kernel_busy_icon:before.pull-right {
9999 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 10033 .notification_widget {
10002 10034 color: #777777;
10003 10035 padding: 1px 12px;
@@ -1,12 +1,12 b''
1 1
2 2 //
3 // Miscellaneous javascript tests
3 // Kernel tests
4 4 //
5 5 casper.notebook_test(function () {
6 6 this.evaluate(function () {
7 7 IPython.notebook.kernel.kernel_info(
8 8 function(msg){
9 IPython._kernel_info_response = msg;
9 IPython._kernel_info_response = msg;
10 10 })
11 11 });
12 12
@@ -24,5 +24,41 b' casper.notebook_test(function () {'
24 24 this.test.assertTrue( kernel_info_response.msg_type === 'kernel_info_reply', 'Kernel info request return kernel_info_reply');
25 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