##// END OF EJS Templates
log exceptions in Comm handlers
MinRK -
Show More
@@ -1,169 +1,183
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 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 // Comm and CommManager bases
10 10 //============================================================================
11 11 /**
12 12 * Base Comm classes
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule comm
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19 "use strict";
20 20
21 21 //-----------------------------------------------------------------------
22 22 // CommManager class
23 23 //-----------------------------------------------------------------------
24 24
25 25 var CommManager = function (kernel) {
26 26 this.comms = {};
27 27 this.targets = {};
28 28 if (kernel !== undefined) {
29 29 this.init_kernel(kernel);
30 30 }
31 31 };
32 32
33 33 CommManager.prototype.init_kernel = function (kernel) {
34 34 // connect the kernel, and register message handlers
35 35 this.kernel = kernel;
36 36 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
37 37 for (var i = 0; i < msg_types.length; i++) {
38 38 var msg_type = msg_types[i];
39 39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
40 40 }
41 41 };
42 42
43 43 CommManager.prototype.register_target = function (target_name, f) {
44 44 // Register a target function for a given target name
45 45 this.targets[target_name] = f;
46 46 };
47 47
48 48 CommManager.prototype.unregister_target = function (target_name, f) {
49 49 // Unregister a target function for a given target name
50 50 delete this.targets[target_name];
51 51 };
52 52
53 53 CommManager.prototype.register_comm = function (comm) {
54 54 // Register a comm in the mapping
55 55 this.comms[comm.comm_id] = comm;
56 56 comm.kernel = this.kernel;
57 57 return comm.comm_id;
58 58 };
59 59
60 60 CommManager.prototype.unregister_comm = function (comm_id) {
61 61 // Remove a comm from the mapping
62 62 delete this.comms[comm_id];
63 63 };
64 64
65 65 // comm message handlers
66 66
67 67 CommManager.prototype.comm_open = function (msg) {
68 68 var content = msg.content;
69 69 var f = this.targets[content.target_name];
70 70 if (f === undefined) {
71 71 console.log("No such target registered: ", content.target_name);
72 72 console.log("Available targets are: ", this.targets);
73 73 return;
74 74 }
75 75 var comm = new Comm(content.comm_id);
76 76 this.register_comm(comm);
77 try {
77 78 f(comm, msg);
79 } catch (e) {
80 console.log("Exception opening new comm:", e, msg);
81 comm.close();
82 this.unregister_comm(comm);
83 }
78 84 };
79 85
80 86 CommManager.prototype.comm_close = function (msg) {
81 87 var content = msg.content;
82 88 var comm = this.comms[content.comm_id];
83 89 if (comm === undefined) {
84 90 return;
85 91 }
86 92 delete this.comms[content.comm_id];
93 try {
87 94 comm.handle_close(msg);
95 } catch (e) {
96 console.log("Exception closing comm: ", e, msg);
97 }
88 98 };
89 99
90 100 CommManager.prototype.comm_msg = function (msg) {
91 101 var content = msg.content;
92 102 var comm = this.comms[content.comm_id];
93 103 if (comm === undefined) {
94 104 return;
95 105 }
106 try {
96 107 comm.handle_msg(msg);
108 } catch (e) {
109 console.log("Exception handling comm msg: ", e, msg);
110 }
97 111 };
98 112
99 113 //-----------------------------------------------------------------------
100 114 // Comm base class
101 115 //-----------------------------------------------------------------------
102 116
103 117 var Comm = function (comm_id, target_name) {
104 118 this.comm_id = comm_id || new IPython.utils.uuid();
105 119 this.target_name = target_name;
106 120 this._msg_callback = this._close_callback = null;
107 121 };
108 122
109 123 // methods for sending messages
110 124 Comm.prototype.open = function (data, callbacks, metadata) {
111 125 var content = {
112 126 comm_id : this.comm_id,
113 127 target_name : this.target_name,
114 128 data : data || {},
115 129 };
116 130 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
117 131 };
118 132
119 133 Comm.prototype.send = function (data, callbacks, metadata) {
120 134 var content = {
121 135 comm_id : this.comm_id,
122 136 data : data || {},
123 137 };
124 138 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
125 139 };
126 140
127 141 Comm.prototype.close = function (data, callbacks, metadata) {
128 142 var content = {
129 143 comm_id : this.comm_id,
130 144 data : data || {},
131 145 };
132 146 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
133 147 };
134 148
135 149 // methods for registering callbacks for incoming messages
136 150 Comm.prototype._register_callback = function (key, callback) {
137 151 this['_' + key + '_callback'] = callback;
138 152 };
139 153
140 154 Comm.prototype.on_msg = function (callback) {
141 155 this._register_callback('msg', callback);
142 156 };
143 157
144 158 Comm.prototype.on_close = function (callback) {
145 159 this._register_callback('close', callback);
146 160 };
147 161
148 162 // methods for handling incoming messages
149 163
150 164 Comm.prototype._maybe_callback = function (key, msg) {
151 165 var callback = this['_' + key + '_callback'];
152 166 if (callback) callback(msg);
153 167 };
154 168
155 169 Comm.prototype.handle_msg = function (msg) {
156 170 this._maybe_callback('msg', msg);
157 171 };
158 172
159 173 Comm.prototype.handle_close = function (msg) {
160 174 this._maybe_callback('close', msg);
161 175 };
162 176
163 177 IPython.CommManager = CommManager;
164 178 IPython.Comm = Comm;
165 179
166 180 return IPython;
167 181
168 182 }(IPython));
169 183
@@ -1,170 +1,185
1 1 """Base class to manage comms"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import sys
15 15
16 16 from IPython.config import LoggingConfigurable
17 17 from IPython.core.prompts import LazyEvaluate
18 18 from IPython.core.getipython import get_ipython
19 19
20 20 from IPython.utils.importstring import import_item
21 21 from IPython.utils.traitlets import Instance, Unicode, Dict, Any
22 22
23 23 from .comm import Comm
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Code
27 27 #-----------------------------------------------------------------------------
28 28
29 29 def lazy_keys(dikt):
30 30 """Return lazy-evaluated string representation of a dictionary's keys
31 31
32 32 Key list is only constructed if it will actually be used.
33 33 Used for debug-logging.
34 34 """
35 35 return LazyEvaluate(lambda d: list(d.keys()))
36 36
37 37
38 38 def with_output(method):
39 39 """method decorator for ensuring output is handled properly in a message handler
40 40
41 41 - sets parent header before entering the method
42 42 - publishes busy/idle
43 43 - flushes stdout/stderr after
44 44 """
45 45 def method_with_output(self, stream, ident, msg):
46 46 parent = msg['header']
47 47 self.shell.set_parent(parent)
48 48 self.shell.kernel._publish_status('busy')
49 49 try:
50 50 return method(self, stream, ident, msg)
51 51 finally:
52 52 sys.stdout.flush()
53 53 sys.stderr.flush()
54 54 self.shell.kernel._publish_status('idle')
55 55
56 56 return method_with_output
57 57
58 58
59 59 class CommManager(LoggingConfigurable):
60 60 """Manager for Comms in the Kernel"""
61 61
62 62 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63 63 def _shell_default(self):
64 64 return get_ipython()
65 65 iopub_socket = Any()
66 66 def _iopub_socket_default(self):
67 67 return self.shell.kernel.iopub_socket
68 68 session = Instance('IPython.kernel.zmq.session.Session')
69 69 def _session_default(self):
70 70 if self.shell is None:
71 71 return
72 72 return self.shell.kernel.session
73 73
74 74 comms = Dict()
75 75 targets = Dict()
76 76
77 77 # Public APIs
78 78
79 79 def register_target(self, target_name, f):
80 80 """Register a callable f for a given target name
81 81
82 f will be called with a Comm object as its only argument
83 when a comm_open message is received with `target`.
82 f will be called with two arguments when a comm_open message is received with `target`:
83
84 - the Comm instance
85 - the `comm_open` message itself.
84 86
85 87 f can be a Python callable or an import string for one.
86 88 """
87 89 if isinstance(f, basestring):
88 90 f = import_item(f)
89 91
90 92 self.targets[target_name] = f
91 93
92 94 def unregister_target(self, target_name, f):
93 95 """Unregister a callable registered with register_target"""
94 96 return self.targets.pop(target_name);
95 97
96 98 def register_comm(self, comm):
97 99 """Register a new comm"""
98 100 comm_id = comm.comm_id
99 101 comm.shell = self.shell
100 102 comm.iopub_socket = self.iopub_socket
101 103 self.comms[comm_id] = comm
102 104 return comm_id
103 105
104 106 def unregister_comm(self, comm_id):
105 107 """Unregister a comm, and close its counterpart"""
106 108 # unlike get_comm, this should raise a KeyError
107 109 comm = self.comms.pop(comm_id)
108 110 comm.close()
109 111
110 112 def get_comm(self, comm_id):
111 113 """Get a comm with a particular id
112 114
113 115 Returns the comm if found, otherwise None.
114 116
115 117 This will not raise an error,
116 118 it will log messages if the comm cannot be found.
117 119 """
118 120 if comm_id not in self.comms:
119 121 self.log.error("No such comm: %s", comm_id)
120 122 self.log.debug("Current comms: %s", lazy_keys(self.comms))
121 123 return
122 124 # call, because we store weakrefs
123 125 comm = self.comms[comm_id]
124 126 return comm
125 127
126 128 # Message handlers
127 129 @with_output
128 130 def comm_open(self, stream, ident, msg):
129 131 """Handler for comm_open messages"""
130 132 content = msg['content']
131 133 comm_id = content['comm_id']
132 134 target_name = content['target_name']
133 135 f = self.targets.get(target_name, None)
134 136 comm = Comm(comm_id=comm_id,
135 137 shell=self.shell,
136 138 iopub_socket=self.iopub_socket,
137 139 primary=False,
138 140 )
139 141 if f is None:
140 142 self.log.error("No such comm target registered: %s", target_name)
141 143 comm.close()
142 144 return
143 145 self.register_comm(comm)
146 try:
144 147 f(comm, msg)
148 except Exception:
149 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
150 comm.close()
151 self.unregister_comm(comm_id)
145 152
146 153 @with_output
147 154 def comm_msg(self, stream, ident, msg):
148 155 """Handler for comm_msg messages"""
149 156 content = msg['content']
150 157 comm_id = content['comm_id']
151 158 comm = self.get_comm(comm_id)
152 159 if comm is None:
153 160 # no such comm
154 161 return
162 try:
155 163 comm.handle_msg(msg)
164 except Exception:
165 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
156 166
157 167 @with_output
158 168 def comm_close(self, stream, ident, msg):
159 169 """Handler for comm_close messages"""
160 170 content = msg['content']
161 171 comm_id = content['comm_id']
162 172 comm = self.get_comm(comm_id)
163 173 if comm is None:
164 174 # no such comm
175 self.log.debug("No such comm to close: %s", comm_id)
165 176 return
166 177 del self.comms[comm_id]
178
179 try:
167 180 comm.handle_close(msg)
181 except Exception:
182 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
168 183
169 184
170 185 __all__ = ['CommManager']
General Comments 0
You need to be logged in to leave comments. Login now