| 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 |