##// END OF EJS Templates
avoid double websocket-close message
MinRK -
Show More
@@ -1,386 +1,388
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 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 // Initialization and connection.
17 17
18 18 var Kernel = function (base_url) {
19 19 this.kernel_id = null;
20 20 this.shell_channel = null;
21 21 this.iopub_channel = null;
22 22 this.base_url = base_url;
23 23 this.running = false;
24 24 this.username = "username";
25 25 this.session_id = utils.uuid();
26 26 this._msg_callbacks = {};
27 27
28 28 if (typeof(WebSocket) !== 'undefined') {
29 29 this.WebSocket = WebSocket;
30 30 } else if (typeof(MozWebSocket) !== 'undefined') {
31 31 this.WebSocket = MozWebSocket;
32 32 } else {
33 33 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.');
34 34 };
35 35 };
36 36
37 37
38 38 Kernel.prototype._get_msg = function (msg_type, content) {
39 39 var msg = {
40 40 header : {
41 41 msg_id : utils.uuid(),
42 42 username : this.username,
43 43 session : this.session_id,
44 44 msg_type : msg_type
45 45 },
46 46 content : content,
47 47 parent_header : {}
48 48 };
49 49 return msg;
50 50 };
51 51
52 52 Kernel.prototype.start = function (notebook_id) {
53 53 var that = this;
54 54 if (!this.running) {
55 55 var qs = $.param({notebook:notebook_id});
56 56 var url = this.base_url + '?' + qs;
57 57 $.post(url,
58 58 $.proxy(that._kernel_started,that),
59 59 'json'
60 60 );
61 61 };
62 62 };
63 63
64 64
65 65 Kernel.prototype.restart = function () {
66 66 $([IPython.events]).trigger('status_restarting.Kernel');
67 67 var that = this;
68 68 if (this.running) {
69 69 this.stop_channels();
70 70 var url = this.kernel_url + "/restart";
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 Kernel.prototype._kernel_started = function (json) {
80 80 console.log("Kernel started: ", json.kernel_id);
81 81 this.running = true;
82 82 this.kernel_id = json.kernel_id;
83 83 this.ws_url = json.ws_url;
84 84 this.kernel_url = this.base_url + "/" + this.kernel_id;
85 85 this.start_channels();
86 86 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
87 87 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
88 88 };
89 89
90 90
91 91 Kernel.prototype._websocket_closed = function(ws_url, early){
92 92 var msg;
93 93 var parent_item = $('body');
94 94 if (early) {
95 95 msg = "Websocket connection to " + ws_url + " could not be established." +
96 96 " You will NOT be able to run code." +
97 97 " Your browser may not be compatible with the websocket version in the server," +
98 98 " or if the url does not look right, there could be an error in the" +
99 99 " server's configuration.";
100 100 } else {
101 this.start_channels();
102 }
101 IPython.notification_widget.set_message('Reconnecting Websockets', 1000);
102 this.start_channels();
103 return;
104 }
103 105 var dialog = $('<div/>');
104 106 dialog.html(msg);
105 107 parent_item.append(dialog);
106 108 dialog.dialog({
107 109 resizable: false,
108 110 modal: true,
109 111 title: "Websocket closed",
110 112 closeText: "",
111 113 close: function(event, ui) {$(this).dialog('destroy').remove();},
112 114 buttons : {
113 115 "OK": function () {
114 116 $(this).dialog('close');
115 117 }
116 118 }
117 119 });
118 120
119 121 };
120 122
121 123 Kernel.prototype.start_channels = function () {
122 124 var that = this;
123 125 this.stop_channels();
124 126 var ws_url = this.ws_url + this.kernel_url;
125 127 console.log("Starting WS:", ws_url);
126 128 this.shell_channel = new this.WebSocket(ws_url + "/shell");
127 129 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
128 130 send_cookie = function(){
129 131 this.send(document.cookie);
130 132 };
131 133 var already_called_onclose = false; // only alert once
132 134 ws_closed_early = function(evt){
133 135 if (already_called_onclose){
134 136 return;
135 137 }
136 138 already_called_onclose = true;
137 139 if ( ! evt.wasClean ){
138 140 that._websocket_closed(ws_url, true);
139 141 }
140 142 };
141 143 ws_closed_late = function(evt){
142 144 if (already_called_onclose){
143 145 return;
144 146 }
145 147 already_called_onclose = true;
146 148 if ( ! evt.wasClean ){
147 149 that._websocket_closed(ws_url, false);
148 150 }
149 151 };
150 152 this.shell_channel.onopen = send_cookie;
151 153 this.shell_channel.onclose = ws_closed_early;
152 154 this.iopub_channel.onopen = send_cookie;
153 155 this.iopub_channel.onclose = ws_closed_early;
154 156 // switch from early-close to late-close message after 1s
155 157 setTimeout(function(){
156 158 that.shell_channel.onclose = ws_closed_late;
157 159 that.iopub_channel.onclose = ws_closed_late;
158 160 }, 1000);
159 161 };
160 162
161 163
162 164 Kernel.prototype.stop_channels = function () {
163 165 if (this.shell_channel !== null) {
164 166 this.shell_channel.onclose = function (evt) {};
165 167 this.shell_channel.close();
166 168 this.shell_channel = null;
167 169 };
168 170 if (this.iopub_channel !== null) {
169 171 this.iopub_channel.onclose = function (evt) {};
170 172 this.iopub_channel.close();
171 173 this.iopub_channel = null;
172 174 };
173 175 };
174 176
175 177 // Main public methods.
176 178
177 179 Kernel.prototype.object_info_request = function (objname, callbacks) {
178 180 // When calling this method pass a callbacks structure of the form:
179 181 //
180 182 // callbacks = {
181 183 // 'object_info_reply': object_into_reply_callback
182 184 // }
183 185 //
184 186 // The object_info_reply_callback will be passed the content object of the
185 187 // object_into_reply message documented here:
186 188 //
187 189 // http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
188 190 if(typeof(objname)!=null && objname!=null)
189 191 {
190 192 var content = {
191 193 oname : objname.toString(),
192 194 };
193 195 var msg = this._get_msg("object_info_request", content);
194 196 this.shell_channel.send(JSON.stringify(msg));
195 197 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
196 198 return msg.header.msg_id;
197 199 }
198 200 return;
199 201 }
200 202
201 203 Kernel.prototype.execute = function (code, callbacks, options) {
202 204 // The options object should contain the options for the execute call. Its default
203 205 // values are:
204 206 //
205 207 // options = {
206 208 // silent : true,
207 209 // user_variables : [],
208 210 // user_expressions : {},
209 211 // allow_stdin : false
210 212 // }
211 213 //
212 214 // When calling this method pass a callbacks structure of the form:
213 215 //
214 216 // callbacks = {
215 217 // 'execute_reply': execute_reply_callback,
216 218 // 'output': output_callback,
217 219 // 'clear_output': clear_output_callback,
218 220 // 'set_next_input': set_next_input_callback
219 221 // }
220 222 //
221 223 // The execute_reply_callback will be passed the content object of the execute_reply
222 224 // message documented here:
223 225 //
224 226 // http://ipython.org/ipython-doc/dev/development/messaging.html#execute
225 227 //
226 228 // The output_callback will be passed msg_type ('stream','display_data','pyout','pyerr')
227 229 // of the output and the content object of the PUB/SUB channel that contains the
228 230 // output:
229 231 //
230 232 // http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
231 233 //
232 234 // The clear_output_callback will be passed a content object that contains
233 235 // stdout, stderr and other fields that are booleans.
234 236 //
235 237 // The set_next_input_callback will bepassed the text that should become the next
236 238 // input cell.
237 239
238 240 var content = {
239 241 code : code,
240 242 silent : true,
241 243 user_variables : [],
242 244 user_expressions : {},
243 245 allow_stdin : false
244 246 };
245 247 $.extend(true, content, options)
246 248 var msg = this._get_msg("execute_request", content);
247 249 this.shell_channel.send(JSON.stringify(msg));
248 250 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
249 251 return msg.header.msg_id;
250 252 };
251 253
252 254
253 255 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
254 256 // When calling this method pass a callbacks structure of the form:
255 257 //
256 258 // callbacks = {
257 259 // 'complete_reply': complete_reply_callback
258 260 // }
259 261 //
260 262 // The complete_reply_callback will be passed the content object of the
261 263 // complete_reply message documented here:
262 264 //
263 265 // http://ipython.org/ipython-doc/dev/development/messaging.html#complete
264 266 callbacks = callbacks || {};
265 267 var content = {
266 268 text : '',
267 269 line : line,
268 270 cursor_pos : cursor_pos
269 271 };
270 272 var msg = this._get_msg("complete_request", content);
271 273 this.shell_channel.send(JSON.stringify(msg));
272 274 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
273 275 return msg.header.msg_id;
274 276 };
275 277
276 278
277 279 Kernel.prototype.interrupt = function () {
278 280 if (this.running) {
279 281 $([IPython.events]).trigger('status_interrupting.Kernel');
280 282 $.post(this.kernel_url + "/interrupt");
281 283 };
282 284 };
283 285
284 286
285 287 Kernel.prototype.kill = function () {
286 288 if (this.running) {
287 289 this.running = false;
288 290 var settings = {
289 291 cache : false,
290 292 type : "DELETE"
291 293 };
292 294 $.ajax(this.kernel_url, settings);
293 295 };
294 296 };
295 297
296 298
297 299 // Reply handlers.
298 300
299 301 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
300 302 var callbacks = this._msg_callbacks[msg_id];
301 303 return callbacks;
302 304 };
303 305
304 306
305 307 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
306 308 this._msg_callbacks[msg_id] = callbacks || {};
307 309 }
308 310
309 311
310 312 Kernel.prototype._handle_shell_reply = function (e) {
311 313 reply = $.parseJSON(e.data);
312 314 var header = reply.header;
313 315 var content = reply.content;
314 316 var msg_type = header.msg_type;
315 317 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
316 318 if (callbacks !== undefined) {
317 319 var cb = callbacks[msg_type];
318 320 if (cb !== undefined) {
319 321 cb(content);
320 322 }
321 323 };
322 324
323 325 if (content.payload !== undefined) {
324 326 var payload = content.payload || [];
325 327 this._handle_payload(callbacks, payload);
326 328 }
327 329 };
328 330
329 331
330 332 Kernel.prototype._handle_payload = function (callbacks, payload) {
331 333 var l = payload.length;
332 334 // Payloads are handled by triggering events because we don't want the Kernel
333 335 // to depend on the Notebook or Pager classes.
334 336 for (var i=0; i<l; i++) {
335 337 if (payload[i].source === 'IPython.zmq.page.page') {
336 338 var data = {'text':payload[i].text}
337 339 $([IPython.events]).trigger('open_with_text.Pager', data);
338 340 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
339 341 if (callbacks.set_next_input !== undefined) {
340 342 callbacks.set_next_input(payload[i].text)
341 343 }
342 344 }
343 345 };
344 346 };
345 347
346 348
347 349 Kernel.prototype._handle_iopub_reply = function (e) {
348 350 reply = $.parseJSON(e.data);
349 351 var content = reply.content;
350 352 var msg_type = reply.header.msg_type;
351 353 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
352 354 if (msg_type !== 'status' && callbacks === undefined) {
353 355 // Message not from one of this notebook's cells and there are no
354 356 // callbacks to handle it.
355 357 return;
356 358 }
357 359 var output_types = ['stream','display_data','pyout','pyerr'];
358 360 if (output_types.indexOf(msg_type) >= 0) {
359 361 var cb = callbacks['output'];
360 362 if (cb !== undefined) {
361 363 cb(msg_type, content);
362 364 }
363 365 } else if (msg_type === 'status') {
364 366 if (content.execution_state === 'busy') {
365 367 $([IPython.events]).trigger('status_busy.Kernel');
366 368 } else if (content.execution_state === 'idle') {
367 369 $([IPython.events]).trigger('status_idle.Kernel');
368 370 } else if (content.execution_state === 'dead') {
369 371 this.stop_channels();
370 372 $([IPython.events]).trigger('status_dead.Kernel');
371 373 };
372 374 } else if (msg_type === 'clear_output') {
373 375 var cb = callbacks['clear_output'];
374 376 if (cb !== undefined) {
375 377 cb(content);
376 378 }
377 379 };
378 380 };
379 381
380 382
381 383 IPython.Kernel = Kernel;
382 384
383 385 return IPython;
384 386
385 387 }(IPython));
386 388
General Comments 0
You need to be logged in to leave comments. Login now