def test_compount_objects(): """ Test serialization for compound object types. """ dataset = ( { 'asdf': b'\x01\x23\x45\x67', 'qwer': 1234.5678, 'zxcv': [ float('inf'), float('-inf'), 0., 0.0000000000000000000001, { 'i': 3, 'j': 4, }, ], }, None, [ True, { 'key1': False, 'key2': [2.71828], }, ], ) for data in dataset: assert data == deserialize(serialize(data))
def test_containers(): """ Test serialization for container types: - list - dict """ dataset = ( [ None, True, False, 1000, 3.14, float('inf'), float('-inf'), 'asdf', b'qwerzxcv\x0f' ], { 'a': None, 'b': True, 'c': False, 'd': 1000, 'e': 3.14, 'f': float('inf'), 'g': float('-inf'), 'h': b'qwerzxcv\x0f', 'i': 'asdf', }, ) for data in dataset: assert data == deserialize(serialize(data))
def call(self, server, method, args=(), kwargs={}, timeout=None): """ Call an RPC method of a server with args and kwargs. If `timeout` is None, blocks indefinitely. If `timeout` is a number, blocks `timeout` seconds and raises RPCTimeoutError on timeout. """ if timeout is None or timeout < 0: timeout = float('inf') sockets = self._sockets if server not in sockets: self.__connect(server) socket = sockets[server] request_id = str(uuid.uuid4()) request = serialize([request_id, method, args, kwargs]) start_time = time.monotonic() events = {} current_time = start_time elapsed_time = current_time - start_time retry_timeout = self._retry_timeout while elapsed_time <= timeout: socket.send(request) timeout_ms = 1000 * max(0, min(retry_timeout, timeout - elapsed_time)) logger.debug('Polling sockets with %s ms timeout', timeout_ms) events = dict(self._poller.poll(timeout=timeout_ms)) if socket in events: break logger.error('No response from "%s" - reconnecting...' % server) self.__disconnect(server) self.__connect(server) socket = sockets[server] current_time = time.monotonic() elapsed_time = current_time - start_time if socket not in events: raise RPCTimeoutError('Service "%s" not responding' % server) response_data = socket.recv() response = deserialize(response_data) [response_id, payload, is_exception] = response if request_id != response_id: raise RPCError('Internal error (request ID does not match)') if is_exception: raise RPCError(payload) return payload
async def _multipoll(self): logger.debug('Multipoll started.') try: while True: for socket, _ in await self.poller.poll(): null, response_data = await socket.recv_multipart() [response_id, payload, is_exception] = deserialize(response_data) self.response_map[response_id].put_nowait(response_data) except asyncio.CancelledError: logger.debug('Multipoll stopped.')
async def __handle_request(self, socket: zmq.Socket, data: bytes): token, null, request_data = data try: request = deserialize(request_data) [request_id, method_name, args, kwargs] = request except (SerializationError, ValueError) as exc: self.__logger.error('Received malformed RPC request!') response_data = str(exc) is_exception = True else: if request_id in self.__cache: self.__logger.debug('Returning request from cache') token, null, response_data = await self.__cache[request_id] else: self.__cache[request_id] = asyncio.Future() try: method = self._rpc_methods[method_name] except KeyError: payload = 'Invalid RPC method name: %s' % method_name is_exception = True self.__logger.error(payload) else: self.__logger.debug( 'Executing "%s" with args "%s" and kwargs "%s"...', method_name, str(args)[:50], str(kwargs)[:50]) try: payload = await method(self, *args, **kwargs) except Exception as exc: self.__logger.error('--- RPC METHOD EXCEPTION ---', exc_info=True) payload = '%s: %s' % (type(exc).__name__, exc) is_exception = True else: is_exception = False self.__logger.debug('Serializing RPC response "%s"...', str(payload)[:50]) response = [request_id, payload, is_exception] response_data = serialize(response) self.__logger.debug('Sending RPC response "%s"...', str(response_data)[:50]) if request_id in self.__cache: self.__cache[request_id].set_result( [token, null, response_data]) await socket.send_multipart([token, null, response_data])
async def call(self, server, method, args=(), kwargs={}, timeout=None): """ Call an RPC method of a server with args and kwargs. If `timeout` is None, blocks indefinitely. If `timeout` is a number, blocks `timeout` seconds and raises RPCTimeoutError on timeout. """ if timeout is None or timeout < 0: timeout = float('inf') # Create message request_id = str(uuid.uuid4()) request = serialize([request_id, method, args, kwargs]) start_time = time.monotonic() elapsed_time = 0 fail_count = 0 while elapsed_time <= timeout: # Send request socket = self.connector.get_socket(server) await self.connector.send(socket, request) timeout_try = max(0, min(self.retry_timeout, timeout - elapsed_time)) try: # Wait response response_data = await asyncio.wait_for( self.multipoller.poll_and_recv(request_id), timeout=timeout_try, ) except asyncio.TimeoutError: pass else: # Parse response response_id, payload, is_exception = deserialize(response_data) if request_id != response_id: raise RPCError('Internal error (request ID does not match)') if is_exception: raise RPCError(payload) return payload elapsed_time = time.monotonic() - start_time raise RPCTimeoutError('Service "%s" not responding' % server)
def __handle_request(self, socket): request_data = socket.recv() try: request = deserialize(request_data) [request_id, method_name, args, kwargs] = request except (SerializationError, ValueError) as exc: self.__logger.error('Received malformed RPC request!') # send empty message to keep REP state machine happy socket.send(b'') return if request_id in self.__cache: # resend response silently self.__logger.debug('Returning request from cache: %s', request_id) response_data = self.__cache[request_id] socket.send(self.__cache[request_id]) return try: method = self._rpc_methods[method_name] except KeyError: payload = 'Invalid RPC method name: %s' % method_name is_exception = True self.__logger.error(payload) else: self.__logger.debug( 'Executing "%s" with args "%s" and kwargs "%s"...', method_name, str(args)[:50], str(kwargs)[:50]) try: payload = method(self, *args, **kwargs) except Exception as exc: self.__logger.error('--- RPC METHOD EXCEPTION ---', exc_info=True) payload = '%s: %s' % (type(exc).__name__, exc) is_exception = True else: is_exception = False self.__logger.debug('Serializing RPC response "%s"...', str(payload)[:50]) response = [request_id, payload, is_exception] response_data = serialize(response) self.__logger.debug('Sending RPC response "%s"...', str(response_data)[:50]) self.__cache[request_id] = response_data socket.send(response_data)
def test_builtin(): """ Test serialization for builtin types: - null - bool - numeric (int/float) - strings (unicode/bytes) """ dataset = ( None, True, False, 1000, 3.14, float('inf'), float('-inf'), 'asdf', b'qwerzxcv\x0f', ) for data in dataset: assert data == deserialize(serialize(data))