##// END OF EJS Templates
Refactoring WebSocket connection failure logic....
Brian E. Granger -
Show More
@@ -1,460 +1,439
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Kernel
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 // Initialization and connection.
23 23 /**
24 24 * A Kernel Class to communicate with the Python kernel
25 25 * @Class Kernel
26 26 */
27 27 var Kernel = function (base_url) {
28 28 this.kernel_id = null;
29 29 this.shell_channel = null;
30 30 this.iopub_channel = null;
31 31 this.base_url = base_url;
32 32 this.running = false;
33 33 this.username = "username";
34 34 this.session_id = utils.uuid();
35 35 this._msg_callbacks = {};
36 36
37 37 if (typeof(WebSocket) !== 'undefined') {
38 38 this.WebSocket = WebSocket;
39 39 } else if (typeof(MozWebSocket) !== 'undefined') {
40 40 this.WebSocket = MozWebSocket;
41 41 } else {
42 42 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
43 43 };
44 44 };
45 45
46 46
47 47 Kernel.prototype._get_msg = function (msg_type, content) {
48 48 var msg = {
49 49 header : {
50 50 msg_id : utils.uuid(),
51 51 username : this.username,
52 52 session : this.session_id,
53 53 msg_type : msg_type
54 54 },
55 55 metadata : {},
56 56 content : content,
57 57 parent_header : {}
58 58 };
59 59 return msg;
60 60 };
61 61
62 62 /**
63 63 * Start the Python kernel
64 64 * @method start
65 65 */
66 66 Kernel.prototype.start = function (notebook_id) {
67 67 var that = this;
68 68 if (!this.running) {
69 69 var qs = $.param({notebook:notebook_id});
70 70 var url = this.base_url + '?' + qs;
71 71 $.post(url,
72 72 $.proxy(that._kernel_started,that),
73 73 'json'
74 74 );
75 75 };
76 76 };
77 77
78 78 /**
79 79 * Restart the python kernel.
80 80 *
81 81 * Emit a 'status_restarting.Kernel' event with
82 82 * the current object as parameter
83 83 *
84 84 * @method restart
85 85 */
86 86 Kernel.prototype.restart = function () {
87 87 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
88 88 var that = this;
89 89 if (this.running) {
90 90 this.stop_channels();
91 91 var url = this.kernel_url + "/restart";
92 92 $.post(url,
93 93 $.proxy(that._kernel_started, that),
94 94 'json'
95 95 );
96 96 };
97 97 };
98 98
99 99
100 100 Kernel.prototype._kernel_started = function (json) {
101 101 console.log("Kernel started: ", json.kernel_id);
102 102 this.running = true;
103 103 this.kernel_id = json.kernel_id;
104 104 this.ws_url = json.ws_url;
105 105 this.kernel_url = this.base_url + "/" + this.kernel_id;
106 106 this.start_channels();
107 107 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
108 108 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
109 109 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
110 110 };
111 111
112 112
113 113 Kernel.prototype._websocket_closed = function(ws_url, early){
114 var msg;
115 var parent_item = $('body');
116 if (early) {
117 msg = "Websocket connection to " + ws_url + " could not be established." +
118 " You will NOT be able to run code." +
119 " Your browser may not be compatible with the websocket version in the server," +
120 " or if the url does not look right, there could be an error in the" +
121 " server's configuration.";
122 } else {
123 IPython.notification_area.widget('kernel').set_message('Reconnecting Websockets', 1000);
124 this.start_channels();
125 return;
126 }
127 var dialog = $('<div/>');
128 dialog.html(msg);
129 parent_item.append(dialog);
130 dialog.dialog({
131 resizable: false,
132 modal: true,
133 title: "Websocket closed",
134 closeText: "",
135 close: function(event, ui) {$(this).dialog('destroy').remove();},
136 buttons : {
137 "OK": function () {
138 $(this).dialog('close');
139 }
140 }
141 });
142
114 this.stop_channels();
115 $([IPython.events]).trigger('websocket_closed.Kernel',
116 {ws_url: ws_url, kernel: this, early: early}
117 );
143 118 };
144 119
145 120 /**
146 121 * Start the `shell`and `iopub` channels.
147 122 * Will stop and restart them if they already exist.
148 123 *
149 124 * @method start_channels
150 125 */
151 126 Kernel.prototype.start_channels = function () {
152 127 var that = this;
153 128 this.stop_channels();
154 129 var ws_url = this.ws_url + this.kernel_url;
155 console.log("Starting WS:", ws_url);
130 console.log("Starting WebSockets:", ws_url);
156 131 this.shell_channel = new this.WebSocket(ws_url + "/shell");
157 132 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
158 133 send_cookie = function(){
159 134 this.send(document.cookie);
160 135 };
161 136 var already_called_onclose = false; // only alert once
162 137 var ws_closed_early = function(evt){
163 138 if (already_called_onclose){
164 139 return;
165 140 }
166 141 already_called_onclose = true;
167 142 if ( ! evt.wasClean ){
168 143 that._websocket_closed(ws_url, true);
169 144 }
170 145 };
171 146 var ws_closed_late = function(evt){
172 147 if (already_called_onclose){
173 148 return;
174 149 }
175 150 already_called_onclose = true;
176 151 if ( ! evt.wasClean ){
177 152 that._websocket_closed(ws_url, false);
178 153 }
179 154 };
180 155 this.shell_channel.onopen = send_cookie;
181 156 this.shell_channel.onclose = ws_closed_early;
182 157 this.iopub_channel.onopen = send_cookie;
183 158 this.iopub_channel.onclose = ws_closed_early;
184 159 // switch from early-close to late-close message after 1s
185 160 setTimeout(function(){
161 if (that.shell_channel !== null) {
186 162 that.shell_channel.onclose = ws_closed_late;
163 }
164 if (that.iopub_channel !== null) {
187 165 that.iopub_channel.onclose = ws_closed_late;
166 }
188 167 }, 1000);
189 168 };
190 169
191 170 /**
192 171 * Start the `shell`and `iopub` channels.
193 172 * @method stop_channels
194 173 */
195 174 Kernel.prototype.stop_channels = function () {
196 175 if (this.shell_channel !== null) {
197 176 this.shell_channel.onclose = function (evt) {};
198 177 this.shell_channel.close();
199 178 this.shell_channel = null;
200 179 };
201 180 if (this.iopub_channel !== null) {
202 181 this.iopub_channel.onclose = function (evt) {};
203 182 this.iopub_channel.close();
204 183 this.iopub_channel = null;
205 184 };
206 185 };
207 186
208 187 // Main public methods.
209 188
210 189 /**
211 190 * Get info on object asynchronoulsy
212 191 *
213 192 * @async
214 193 * @param objname {string}
215 194 * @param callback {dict}
216 195 * @method object_info_request
217 196 *
218 197 * @example
219 198 *
220 199 * When calling this method pass a callbacks structure of the form:
221 200 *
222 201 * callbacks = {
223 202 * 'object_info_reply': object_info_reply_callback
224 203 * }
225 204 *
226 205 * The `object_info_reply_callback` will be passed the content object of the
227 206 *
228 207 * `object_into_reply` message documented in
229 208 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
230 209 */
231 210 Kernel.prototype.object_info_request = function (objname, callbacks) {
232 211 if(typeof(objname)!=null && objname!=null)
233 212 {
234 213 var content = {
235 214 oname : objname.toString(),
236 215 };
237 216 var msg = this._get_msg("object_info_request", content);
238 217 this.shell_channel.send(JSON.stringify(msg));
239 218 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
240 219 return msg.header.msg_id;
241 220 }
242 221 return;
243 222 }
244 223
245 224 /**
246 225 * Execute given code into kernel, and pass result to callback.
247 226 *
248 227 * @async
249 228 * @method execute
250 229 * @param {string} code
251 230 * @param callback {Object} With the following keys
252 231 * @param callback.'execute_reply' {function}
253 232 * @param callback.'output' {function}
254 233 * @param callback.'clear_output' {function}
255 234 * @param callback.'set_next_input' {function}
256 235 * @param {object} [options]
257 236 * @param [options.silent=false] {Boolean}
258 237 * @param [options.user_expressions=empty_dict] {Dict}
259 238 * @param [options.user_variables=empty_list] {List od Strings}
260 239 * @param [options.allow_stdin=false] {Boolean} true|false
261 240 *
262 241 * @example
263 242 *
264 243 * The options object should contain the options for the execute call. Its default
265 244 * values are:
266 245 *
267 246 * options = {
268 247 * silent : true,
269 248 * user_variables : [],
270 249 * user_expressions : {},
271 250 * allow_stdin : false
272 251 * }
273 252 *
274 253 * When calling this method pass a callbacks structure of the form:
275 254 *
276 255 * callbacks = {
277 256 * 'execute_reply': execute_reply_callback,
278 257 * 'output': output_callback,
279 258 * 'clear_output': clear_output_callback,
280 259 * 'set_next_input': set_next_input_callback
281 260 * }
282 261 *
283 262 * The `execute_reply_callback` will be passed the content and metadata
284 263 * objects of the `execute_reply` message documented
285 264 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
286 265 *
287 266 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
288 267 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
289 268 * output:
290 269 *
291 270 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
292 271 *
293 272 * The `clear_output_callback` will be passed a content object that contains
294 273 * stdout, stderr and other fields that are booleans, as well as the metadata object.
295 274 *
296 275 * The `set_next_input_callback` will be passed the text that should become the next
297 276 * input cell.
298 277 */
299 278 Kernel.prototype.execute = function (code, callbacks, options) {
300 279
301 280 var content = {
302 281 code : code,
303 282 silent : true,
304 283 user_variables : [],
305 284 user_expressions : {},
306 285 allow_stdin : false
307 286 };
308 287 $.extend(true, content, options)
309 288 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
310 289 var msg = this._get_msg("execute_request", content);
311 290 this.shell_channel.send(JSON.stringify(msg));
312 291 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
313 292 return msg.header.msg_id;
314 293 };
315 294
316 295 /**
317 296 * When calling this method pass a callbacks structure of the form:
318 297 *
319 298 * callbacks = {
320 299 * 'complete_reply': complete_reply_callback
321 300 * }
322 301 *
323 302 * The `complete_reply_callback` will be passed the content object of the
324 303 * `complete_reply` message documented
325 304 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
326 305 *
327 306 * @method complete
328 307 * @param line {integer}
329 308 * @param cursor_pos {integer}
330 309 * @param {dict} callbacks
331 310 * @param callbacks.complete_reply {function} `complete_reply_callback`
332 311 *
333 312 */
334 313 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
335 314 callbacks = callbacks || {};
336 315 var content = {
337 316 text : '',
338 317 line : line,
339 318 cursor_pos : cursor_pos
340 319 };
341 320 var msg = this._get_msg("complete_request", content);
342 321 this.shell_channel.send(JSON.stringify(msg));
343 322 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
344 323 return msg.header.msg_id;
345 324 };
346 325
347 326
348 327 Kernel.prototype.interrupt = function () {
349 328 if (this.running) {
350 329 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
351 330 $.post(this.kernel_url + "/interrupt");
352 331 };
353 332 };
354 333
355 334
356 335 Kernel.prototype.kill = function () {
357 336 if (this.running) {
358 337 this.running = false;
359 338 var settings = {
360 339 cache : false,
361 340 type : "DELETE"
362 341 };
363 342 $.ajax(this.kernel_url, settings);
364 343 };
365 344 };
366 345
367 346
368 347 // Reply handlers.
369 348
370 349 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
371 350 var callbacks = this._msg_callbacks[msg_id];
372 351 return callbacks;
373 352 };
374 353
375 354
376 355 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
377 356 this._msg_callbacks[msg_id] = callbacks || {};
378 357 }
379 358
380 359
381 360 Kernel.prototype._handle_shell_reply = function (e) {
382 361 var reply = $.parseJSON(e.data);
383 362 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
384 363 var header = reply.header;
385 364 var content = reply.content;
386 365 var metadata = reply.metadata;
387 366 var msg_type = header.msg_type;
388 367 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
389 368 if (callbacks !== undefined) {
390 369 var cb = callbacks[msg_type];
391 370 if (cb !== undefined) {
392 371 cb(content, metadata);
393 372 }
394 373 };
395 374
396 375 if (content.payload !== undefined) {
397 376 var payload = content.payload || [];
398 377 this._handle_payload(callbacks, payload);
399 378 }
400 379 };
401 380
402 381
403 382 Kernel.prototype._handle_payload = function (callbacks, payload) {
404 383 var l = payload.length;
405 384 // Payloads are handled by triggering events because we don't want the Kernel
406 385 // to depend on the Notebook or Pager classes.
407 386 for (var i=0; i<l; i++) {
408 387 if (payload[i].source === 'IPython.zmq.page.page') {
409 388 var data = {'text':payload[i].text}
410 389 $([IPython.events]).trigger('open_with_text.Pager', data);
411 390 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
412 391 if (callbacks.set_next_input !== undefined) {
413 392 callbacks.set_next_input(payload[i].text)
414 393 }
415 394 }
416 395 };
417 396 };
418 397
419 398
420 399 Kernel.prototype._handle_iopub_reply = function (e) {
421 400 var reply = $.parseJSON(e.data);
422 401 var content = reply.content;
423 402 var msg_type = reply.header.msg_type;
424 403 var metadata = reply.metadata;
425 404 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
426 405 if (msg_type !== 'status' && callbacks === undefined) {
427 406 // Message not from one of this notebook's cells and there are no
428 407 // callbacks to handle it.
429 408 return;
430 409 }
431 410 var output_types = ['stream','display_data','pyout','pyerr'];
432 411 if (output_types.indexOf(msg_type) >= 0) {
433 412 var cb = callbacks['output'];
434 413 if (cb !== undefined) {
435 414 cb(msg_type, content, metadata);
436 415 }
437 416 } else if (msg_type === 'status') {
438 417 if (content.execution_state === 'busy') {
439 418 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
440 419 } else if (content.execution_state === 'idle') {
441 420 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
442 421 } else if (content.execution_state === 'dead') {
443 422 this.stop_channels();
444 423 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
445 424 };
446 425 } else if (msg_type === 'clear_output') {
447 426 var cb = callbacks['clear_output'];
448 427 if (cb !== undefined) {
449 428 cb(content, metadata);
450 429 }
451 430 };
452 431 };
453 432
454 433
455 434 IPython.Kernel = Kernel;
456 435
457 436 return IPython;
458 437
459 438 }(IPython));
460 439
@@ -1,141 +1,188
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notification widget
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14 var utils = IPython.utils;
15 15
16 16
17 17 var NotificationArea = function (selector) {
18 18 this.selector = selector;
19 19 if (this.selector !== undefined) {
20 20 this.element = $(selector);
21 21 }
22 22 this.widget_dict = {};
23 23 };
24 24
25 25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 26 var uuid = utils.uuid();
27 27 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 29 var tdiv = $('<div>')
30 30 .attr('id',uuid)
31 31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 32 .addClass('border-box-sizing')
33 33 .addClass(css_class)
34 34 .hide()
35 35 .text(msg);
36 36
37 37 $(this.selector).append(tdiv);
38 38 var tmout = Math.max(1500,(timeout||1500));
39 39 tdiv.fadeIn(100);
40 40
41 41 setTimeout(function () {
42 42 tdiv.fadeOut(100, function () {tdiv.remove();});
43 43 }, tmout);
44 44 };
45 45
46 46 NotificationArea.prototype.widget = function(name) {
47 47 if(this.widget_dict[name] == undefined) {
48 48 return this.new_notification_widget(name);
49 49 }
50 50 return this.get_widget(name);
51 51 };
52 52
53 53 NotificationArea.prototype.get_widget = function(name) {
54 54 if(this.widget_dict[name] == undefined) {
55 55 throw('no widgets with this name');
56 56 }
57 57 return this.widget_dict[name];
58 58 };
59 59
60 60 NotificationArea.prototype.new_notification_widget = function(name) {
61 61 if(this.widget_dict[name] != undefined) {
62 62 throw('widget with that name already exists ! ');
63 63 }
64 64 var div = $('<div/>').attr('id','notification_'+name);
65 65 $(this.selector).append(div);
66 66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
67 67 return this.widget_dict[name];
68 68 };
69 69
70 70 NotificationArea.prototype.init_notification_widgets = function() {
71 71 var knw = this.new_notification_widget('kernel');
72 72
73 73 // Kernel events
74 74 $([IPython.events]).on('status_idle.Kernel',function () {
75 75 IPython.save_widget.update_document_title();
76 76 knw.set_message('Kernel Idle',200);
77 77 }
78 78 );
79 79
80 80 $([IPython.events]).on('status_busy.Kernel',function () {
81 81 window.document.title='(Busy) '+window.document.title;
82 82 knw.set_message("Kernel busy");
83 83 });
84 84
85 85 $([IPython.events]).on('status_restarting.Kernel',function () {
86 86 IPython.save_widget.update_document_title();
87 87 knw.set_message("Restarting kernel",1000);
88 88 });
89 89
90 90 $([IPython.events]).on('status_interrupting.Kernel',function () {
91 91 knw.set_message("Interrupting kernel");
92 92 });
93 93
94 94 $([IPython.events]).on('status_dead.Kernel',function () {
95 95 var dialog = $('<div/>');
96 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
96 dialog.html('The kernel has died, would you like to restart it?' +
97 ' If you do not restart the kernel, you will be able to save' +
98 ' the notebook, but running code will not work until the notebook' +
99 ' is reopened.'
100 );
97 101 $(document).append(dialog);
98 102 dialog.dialog({
99 103 resizable: false,
100 104 modal: true,
101 105 title: "Dead kernel",
106 close: function(event, ui) {$(this).dialog('destroy').remove();},
102 107 buttons : {
103 108 "Restart": function () {
104 109 $([IPython.events]).trigger('status_restarting.Kernel');
105 110 IPython.notebook.start_kernel();
106 111 $(this).dialog('close');
107 112 },
108 "Continue running": function () {
113 "Don't restart": function () {
109 114 $(this).dialog('close');
110 115 }
111 116 }
112 117 });
113 118 });
114 119
120 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
121 var kernel = data.kernel;
122 var ws_url = data.ws_url;
123 var early = data.early;
124 var msg;
125 console.log(early);
126 if (!early) {
127 knw.set_message('Reconnecting WebSockets', 1000);
128 setTimeout(function () {
129 kernel.start_channels();
130 }, 5000);
131 return;
132 }
133 console.log('WebSocket connection failed: ', ws_url)
134 msg = "A WebSocket connection to could not be established." +
135 " You will NOT be able to run code. Check your" +
136 " network connection or notebook server configuration.";
137 var dialog = $('<div/>');
138 dialog.html(msg);
139 $(document).append(dialog);
140 dialog.dialog({
141 resizable: false,
142 modal: true,
143 title: "WebSocket connection failed",
144 closeText: "",
145 close: function(event, ui) {$(this).dialog('destroy').remove();},
146 buttons : {
147 "OK": function () {
148 $(this).dialog('close');
149 },
150 "Reconnect": function () {
151 knw.set_message('Reconnecting WebSockets', 1000);
152 setTimeout(function () {
153 kernel.start_channels();
154 }, 5000);
155 $(this).dialog('close');
156 }
157 }
158 });
159 });
160
161
115 162 var nnw = this.new_notification_widget('notebook');
116 163
117 164 // Notebook events
118 165 $([IPython.events]).on('notebook_loading.Notebook', function () {
119 166 nnw.set_message("Loading notebook",500);
120 167 });
121 168 $([IPython.events]).on('notebook_loaded.Notebook', function () {
122 169 nnw.set_message("Notebook loaded",500);
123 170 });
124 171 $([IPython.events]).on('notebook_saving.Notebook', function () {
125 172 nnw.set_message("Saving notebook",500);
126 173 });
127 174 $([IPython.events]).on('notebook_saved.Notebook', function () {
128 175 nnw.set_message("Notebook saved",2000);
129 176 });
130 177 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
131 178 nnw.set_message("Notebook save failed");
132 179 });
133 180
134 181 };
135 182
136 183 IPython.NotificationArea = NotificationArea;
137 184
138 185 return IPython;
139 186
140 187 }(IPython));
141 188
General Comments 0
You need to be logged in to leave comments. Login now