OLD | NEW |
| (Empty) |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """ | |
6 The metaclasses used by the mojo python bindings for interfaces. | |
7 | |
8 It is splitted from mojo_bindings.reflection because it uses some generated code | |
9 that would create a cyclic dependency. | |
10 """ | |
11 | |
12 import logging | |
13 import sys | |
14 | |
15 # pylint: disable=F0401 | |
16 import interface_control_messages_mojom | |
17 import mojo_bindings.messaging as messaging | |
18 import mojo_bindings.promise as promise | |
19 import mojo_bindings.reflection as reflection | |
20 import mojo_bindings.serialization as serialization | |
21 import mojo_system | |
22 | |
23 | |
24 class MojoInterfaceType(type): | |
25 """Meta class for interfaces. | |
26 | |
27 Usage: | |
28 class MyInterface(object): | |
29 __metaclass__ = MojoInterfaceType | |
30 DESCRIPTOR = { | |
31 'fully_qualified_name': 'service::MyInterface' | |
32 'version': 3, | |
33 'methods': [ | |
34 { | |
35 'name': 'FireAndForget', | |
36 'ordinal': 0, | |
37 'parameters': [ | |
38 SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0), | |
39 ] | |
40 }, | |
41 { | |
42 'name': 'Ping', | |
43 'ordinal': 1, | |
44 'parameters': [ | |
45 SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0), | |
46 ], | |
47 'responses': [ | |
48 SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0), | |
49 ], | |
50 }, | |
51 ], | |
52 } | |
53 """ | |
54 | |
55 def __new__(mcs, name, bases, dictionary): | |
56 # If one of the base class is already an interface type, do not edit the | |
57 # class. | |
58 for base in bases: | |
59 if isinstance(base, mcs): | |
60 return type.__new__(mcs, name, bases, dictionary) | |
61 | |
62 descriptor = dictionary.pop('DESCRIPTOR', {}) | |
63 | |
64 methods = [_MethodDescriptor(x) for x in descriptor.get('methods', [])] | |
65 for method in methods: | |
66 dictionary[method.name] = _NotImplemented | |
67 fully_qualified_name = descriptor['fully_qualified_name'] | |
68 | |
69 interface_manager = InterfaceManager( | |
70 name, descriptor['version'], methods, fully_qualified_name) | |
71 dictionary.update({ | |
72 'manager': None, | |
73 '_interface_manager': interface_manager, | |
74 }) | |
75 | |
76 interface_class = type.__new__(mcs, name, bases, dictionary) | |
77 interface_manager.interface_class = interface_class | |
78 return interface_class | |
79 | |
80 @property | |
81 def manager(cls): | |
82 return cls._interface_manager | |
83 | |
84 # Prevent adding new attributes, or mutating constants. | |
85 def __setattr__(cls, key, value): | |
86 raise AttributeError('can\'t set attribute') | |
87 | |
88 # Prevent deleting constants. | |
89 def __delattr__(cls, key): | |
90 raise AttributeError('can\'t delete attribute') | |
91 | |
92 | |
93 class InterfaceManager(object): | |
94 """ | |
95 Manager for an interface class. The manager contains the operation that allows | |
96 to bind an implementation to a pipe, or to generate a proxy for an interface | |
97 over a pipe. | |
98 """ | |
99 | |
100 def __init__(self, name, version, methods, service_name): | |
101 self.name = name | |
102 self.version = version | |
103 self.methods = methods | |
104 self.service_name = service_name | |
105 self.interface_class = None | |
106 self._proxy_class = None | |
107 self._stub_class = None | |
108 | |
109 def Proxy(self, handle, version=0): | |
110 router = messaging.Router(handle) | |
111 error_handler = _ProxyErrorHandler() | |
112 router.SetErrorHandler(error_handler) | |
113 router.Start() | |
114 return self._InternalProxy(router, error_handler, version) | |
115 | |
116 # pylint: disable=W0212 | |
117 def Bind(self, impl, handle): | |
118 router = messaging.Router(handle) | |
119 router.SetIncomingMessageReceiver(self._Stub(impl)) | |
120 error_handler = _ProxyErrorHandler() | |
121 router.SetErrorHandler(error_handler) | |
122 | |
123 # Retain the router, until an error happen. | |
124 retainer = _Retainer(router) | |
125 def Cleanup(_): | |
126 retainer.release() | |
127 error_handler.AddCallback(Cleanup) | |
128 | |
129 # Give an instance manager to the implementation to allow it to close | |
130 # the connection. | |
131 impl.manager = InstanceManager(self, router, error_handler) | |
132 | |
133 router.Start() | |
134 | |
135 def NewRequest(self): | |
136 pipe = mojo_system.MessagePipe() | |
137 return (self.Proxy(pipe.handle0), reflection.InterfaceRequest(pipe.handle1)) | |
138 | |
139 def _InternalProxy(self, router, error_handler, version): | |
140 if error_handler is None: | |
141 error_handler = _ProxyErrorHandler() | |
142 | |
143 if not self._proxy_class: | |
144 dictionary = { | |
145 '__module__': __name__, | |
146 '__init__': _ProxyInit, | |
147 } | |
148 for method in self.methods: | |
149 dictionary[method.name] = _ProxyMethodCall(method) | |
150 self._proxy_class = type( | |
151 '%sProxy' % self.name, | |
152 (self.interface_class, reflection.InterfaceProxy), | |
153 dictionary) | |
154 | |
155 proxy = self._proxy_class(router, error_handler) | |
156 # Give an instance manager to the proxy to allow to close the connection. | |
157 proxy.manager = ProxyInstanceManager( | |
158 self, proxy, router, error_handler, version) | |
159 return proxy | |
160 | |
161 def _Stub(self, impl): | |
162 if not self._stub_class: | |
163 accept_method = _StubAccept(self.methods) | |
164 dictionary = { | |
165 '__module__': __name__, | |
166 '__init__': _StubInit, | |
167 'Accept': accept_method, | |
168 'AcceptWithResponder': accept_method, | |
169 } | |
170 self._stub_class = type('%sStub' % self.name, | |
171 (messaging.MessageReceiverWithResponder,), | |
172 dictionary) | |
173 return self._stub_class(impl) | |
174 | |
175 | |
176 class InstanceManager(object): | |
177 """ | |
178 Manager for the implementation of an interface or a proxy. The manager allows | |
179 to control the connection over the pipe. | |
180 """ | |
181 def __init__(self, interface_manager, router, error_handler): | |
182 self.interface_manager = interface_manager | |
183 self._router = router | |
184 self._error_handler = error_handler | |
185 assert self._error_handler is not None | |
186 | |
187 def Close(self): | |
188 self._error_handler.OnClose() | |
189 self._router.Close() | |
190 | |
191 def PassMessagePipe(self): | |
192 self._error_handler.OnClose() | |
193 return self._router.PassMessagePipe() | |
194 | |
195 def AddOnErrorCallback(self, callback): | |
196 self._error_handler.AddCallback(lambda _: callback(), False) | |
197 | |
198 | |
199 class ProxyInstanceManager(InstanceManager): | |
200 """ | |
201 Manager for the implementation of a proxy. The manager allows to control the | |
202 connection over the pipe. | |
203 """ | |
204 def __init__(self, interface_manager, proxy, router, error_handler, version): | |
205 super(ProxyInstanceManager, self).__init__( | |
206 interface_manager, router, error_handler) | |
207 self.proxy = proxy | |
208 self.version = version | |
209 self._run_method = _ProxyMethodCall(_BaseMethodDescriptor( | |
210 'Run', | |
211 interface_control_messages_mojom.RUN_MESSAGE_ID, | |
212 interface_control_messages_mojom.RunMessageParams, | |
213 interface_control_messages_mojom.RunResponseMessageParams)) | |
214 self._run_or_close_pipe_method = _ProxyMethodCall(_BaseMethodDescriptor( | |
215 'RunOrClosePipe', | |
216 interface_control_messages_mojom.RUN_OR_CLOSE_PIPE_MESSAGE_ID, | |
217 interface_control_messages_mojom.RunOrClosePipeMessageParams, | |
218 None)) | |
219 | |
220 def QueryVersion(self): | |
221 params = interface_control_messages_mojom.RunMessageParams() | |
222 params.reserved0 = 16 | |
223 params.reserved1 = 0 | |
224 params.query_version = ( | |
225 interface_control_messages_mojom.QueryVersion()) | |
226 def ToVersion(r): | |
227 self.version = r.query_version_result.version | |
228 return self.version | |
229 return self._run_method(self.proxy, **params.AsDict()).Then(ToVersion) | |
230 | |
231 def RequireVersion(self, version): | |
232 if self.version >= version: | |
233 return | |
234 self.version = version | |
235 params = interface_control_messages_mojom.RunOrClosePipeMessageParams() | |
236 params.reserved0 = 16 | |
237 params.reserved1 = 0 | |
238 params.require_version = interface_control_messages_mojom.RequireVersion() | |
239 params.require_version.version = version | |
240 return self._run_or_close_pipe_method(self.proxy, **params.AsDict()) | |
241 | |
242 | |
243 class _BaseMethodDescriptor(object): | |
244 def __init__(self, name, ordinal, parameters_struct, response_struct): | |
245 self.name = name | |
246 self.ordinal = ordinal | |
247 self.parameters_struct = parameters_struct | |
248 self.response_struct = response_struct | |
249 | |
250 | |
251 class _MethodDescriptor(_BaseMethodDescriptor): | |
252 def __init__(self, descriptor): | |
253 name = descriptor['name'] | |
254 super(_MethodDescriptor, self).__init__( | |
255 name, | |
256 descriptor['ordinal'], | |
257 _ConstructParameterStruct( | |
258 descriptor['parameters'], name, "Parameters"), | |
259 _ConstructParameterStruct( | |
260 descriptor.get('responses'), name, "Responses")) | |
261 | |
262 | |
263 def _ConstructParameterStruct(descriptor, name, suffix): | |
264 if descriptor is None: | |
265 return None | |
266 parameter_dictionary = { | |
267 '__metaclass__': reflection.MojoStructType, | |
268 '__module__': __name__, | |
269 'DESCRIPTOR': descriptor, | |
270 } | |
271 return reflection.MojoStructType( | |
272 '%s%s' % (name, suffix), | |
273 (object,), | |
274 parameter_dictionary) | |
275 | |
276 | |
277 class _ProxyErrorHandler(messaging.ConnectionErrorHandler): | |
278 def __init__(self): | |
279 messaging.ConnectionErrorHandler.__init__(self) | |
280 self._callbacks = dict() | |
281 | |
282 def OnError(self, result): | |
283 if self._callbacks is None: | |
284 return | |
285 exception = messaging.MessagingException('Mojo error: %d' % result) | |
286 for (callback, _) in self._callbacks.iteritems(): | |
287 callback(exception) | |
288 self._callbacks = None | |
289 | |
290 def OnClose(self): | |
291 if self._callbacks is None: | |
292 return | |
293 exception = messaging.MessagingException('Router has been closed.') | |
294 for (callback, call_on_close) in self._callbacks.iteritems(): | |
295 if call_on_close: | |
296 callback(exception) | |
297 self._callbacks = None | |
298 | |
299 def AddCallback(self, callback, call_on_close=True): | |
300 if self._callbacks is not None: | |
301 self._callbacks[callback] = call_on_close | |
302 | |
303 def RemoveCallback(self, callback): | |
304 if self._callbacks: | |
305 del self._callbacks[callback] | |
306 | |
307 | |
308 class _Retainer(object): | |
309 | |
310 # Set to force instances to be retained. | |
311 _RETAINED = set() | |
312 | |
313 def __init__(self, retained): | |
314 self._retained = retained | |
315 _Retainer._RETAINED.add(self) | |
316 | |
317 def release(self): | |
318 self._retained = None | |
319 _Retainer._RETAINED.remove(self) | |
320 | |
321 | |
322 def _ProxyInit(self, router, error_handler): | |
323 self._router = router | |
324 self._error_handler = error_handler | |
325 | |
326 | |
327 # pylint: disable=W0212 | |
328 def _ProxyMethodCall(method): | |
329 flags = messaging.NO_FLAG | |
330 if method.response_struct: | |
331 flags = messaging.MESSAGE_EXPECTS_RESPONSE_FLAG | |
332 def _Call(self, *args, **kwargs): | |
333 def GenerationMethod(resolve, reject): | |
334 message = _GetMessage(method, flags, None, *args, **kwargs) | |
335 if method.response_struct: | |
336 def Accept(message): | |
337 try: | |
338 assert message.header.message_type == method.ordinal | |
339 payload = message.payload | |
340 response = method.response_struct.Deserialize( | |
341 serialization.RootDeserializationContext(payload.data, | |
342 payload.handles)) | |
343 as_dict = response.AsDict() | |
344 if len(as_dict) == 1: | |
345 value = as_dict.values()[0] | |
346 if not isinstance(value, dict): | |
347 response = value | |
348 resolve(response) | |
349 return True | |
350 except Exception as e: | |
351 # Adding traceback similarly to python 3.0 (pep-3134) | |
352 e.__traceback__ = sys.exc_info()[2] | |
353 reject(e) | |
354 return False | |
355 finally: | |
356 self._error_handler.RemoveCallback(reject) | |
357 | |
358 self._error_handler.AddCallback(reject) | |
359 if not self._router.AcceptWithResponder( | |
360 message, messaging.ForwardingMessageReceiver(Accept)): | |
361 self._error_handler.RemoveCallback(reject) | |
362 reject(messaging.MessagingException("Unable to send message.")) | |
363 else: | |
364 if (self._router.Accept(message)): | |
365 resolve(None) | |
366 else: | |
367 reject(messaging.MessagingException("Unable to send message.")) | |
368 return promise.Promise(GenerationMethod) | |
369 return _Call | |
370 | |
371 | |
372 def _GetMessageWithStruct(struct, ordinal, flags, request_id): | |
373 header = messaging.MessageHeader( | |
374 ordinal, flags, 0 if request_id is None else request_id) | |
375 data = header.Serialize() | |
376 (payload, handles) = struct.Serialize() | |
377 data.extend(payload) | |
378 return messaging.Message(data, handles, header) | |
379 | |
380 | |
381 def _GetMessage(method, flags, request_id, *args, **kwargs): | |
382 if flags == messaging.MESSAGE_IS_RESPONSE_FLAG: | |
383 struct = method.response_struct(*args, **kwargs) | |
384 else: | |
385 struct = method.parameters_struct(*args, **kwargs) | |
386 return _GetMessageWithStruct(struct, method.ordinal, flags, request_id) | |
387 | |
388 | |
389 def _StubInit(self, impl): | |
390 self.impl = impl | |
391 | |
392 | |
393 def _StubAccept(methods): | |
394 methods_by_ordinal = dict((m.ordinal, m) for m in methods) | |
395 def Accept(self, message, responder=None): | |
396 try: | |
397 header = message.header | |
398 assert header.expects_response == bool(responder) | |
399 if header.message_type == interface_control_messages_mojom.RUN_MESSAGE_ID: | |
400 return _RunMessage(self.impl.manager, message, responder) | |
401 if (header.message_type == | |
402 interface_control_messages_mojom.RUN_OR_CLOSE_PIPE_MESSAGE_ID): | |
403 return _RunMessageOrClosePipe(self.impl.manager, message) | |
404 assert header.message_type in methods_by_ordinal | |
405 method = methods_by_ordinal[header.message_type] | |
406 payload = message.payload | |
407 parameters = method.parameters_struct.Deserialize( | |
408 serialization.RootDeserializationContext( | |
409 payload.data, payload.handles)).AsDict() | |
410 response = getattr(self.impl, method.name)(**parameters) | |
411 if header.expects_response: | |
412 @promise.async | |
413 def SendResponse(response): | |
414 if isinstance(response, dict): | |
415 response_message = _GetMessage(method, | |
416 messaging.MESSAGE_IS_RESPONSE_FLAG, | |
417 header.request_id, | |
418 **response) | |
419 else: | |
420 response_message = _GetMessage(method, | |
421 messaging.MESSAGE_IS_RESPONSE_FLAG, | |
422 header.request_id, | |
423 response) | |
424 return responder.Accept(response_message) | |
425 p = SendResponse(response) | |
426 if self.impl.manager: | |
427 # Close the connection in case of error. | |
428 p.Catch(lambda _: self.impl.manager.Close()) | |
429 return True | |
430 # pylint: disable=W0702 | |
431 except: | |
432 # Close the connection in case of error. | |
433 logging.warning( | |
434 'Error occured in accept method. Connection will be closed.') | |
435 logging.debug("Exception", exc_info=True) | |
436 if self.impl.manager: | |
437 self.impl.manager.Close() | |
438 return False | |
439 return Accept | |
440 | |
441 | |
442 def _RunMessage(manager, message, responder): | |
443 response = interface_control_messages_mojom.RunResponseMessageParams() | |
444 response.reserved0 = 16 | |
445 response.reserved1 = 0 | |
446 response.query_version_result = ( | |
447 interface_control_messages_mojom.QueryVersionResult()) | |
448 response.query_version_result.version = manager.interface_manager.version | |
449 response_message = _GetMessageWithStruct( | |
450 response, | |
451 interface_control_messages_mojom.RUN_MESSAGE_ID, | |
452 messaging.MESSAGE_IS_RESPONSE_FLAG, | |
453 message.header.request_id) | |
454 return responder.Accept(response_message) | |
455 | |
456 | |
457 def _RunMessageOrClosePipe(manager, message): | |
458 payload = message.payload | |
459 query = ( | |
460 interface_control_messages_mojom.RunOrClosePipeMessageParams.Deserialize( | |
461 serialization.RootDeserializationContext(payload.data, | |
462 payload.handles))) | |
463 return query.require_version.version <= manager.interface_manager.version | |
464 | |
465 | |
466 def _NotImplemented(*_1, **_2): | |
467 raise NotImplementedError() | |
OLD | NEW |