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 |
} |
|
|
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. |
|
|
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. |
|
|
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 ( |
|
|
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( |
|
|
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 ( |
|
|
44 |
|
|
|
43 | if (success) { | |
|
44 | success(data, status, xhr); | |
|
45 | 45 | } |
|
46 | 46 | }, |
|
47 |
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 |
|
|
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 |
|
|
|
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