async def _call_async(self, method_name: str, *args, **kwargs): """ Sends a request to the socket and then wait for the reply. To deal with multiple, asynchronous requests we do not expect that the receive reply task scheduled from this call is the one that receives this call's reply and instead rely on Events to signal across multiple _async_call/_recv_reply tasks. """ request = utils.rpc_request(method_name, *args, **kwargs) _log.debug("Sending request: %s", request) # setup an event to notify us when the reply is received (potentially by a task scheduled by # another call to _async_call). we do this before we send the request to catch the case # where the reply comes back before we re-enter this thread self._events[request.id] = asyncio.Event() # schedule a task to receive the reply to ensure we have a task to receive the reply asyncio.ensure_future(self._recv_reply()) await self._async_socket.send_multipart([to_msgpack(request)]) await self._events[request.id].wait() reply = self._replies.pop(request.id) if isinstance(reply, RPCError): raise utils.RPCError(reply.error) else: return reply.result
def call(self, method_name: str, *args, rpc_timeout: float = None, **kwargs): """ Send JSON RPC request to a backend socket and receive reply Note that this uses the default event loop to run in a blocking manner. If you would rather run in an async fashion or provide your own event loop then use .async_call instead :param method_name: Method name :param args: Args that will be passed to the remote function :param float rpc_timeout: Timeout in seconds for Server response, set to None to disable the timeout :param kwargs: Keyword args that will be passed to the remote function """ request = utils.rpc_request(method_name, *args, **kwargs) _log.debug("Sending request: %s", request) self._socket.send_multipart([to_msgpack(request)]) # if an rpc_timeout override is not specified, use the one set in the Client attributes if rpc_timeout is None: rpc_timeout = self.rpc_timeout start_time = time.time() while True: # Need to keep track of timeout manually in case this loop runs more than once. We subtract off already # elapsed time from the timeout. The call to max is to make sure we don't send a negative value # which would throw an error. timeout = max((start_time + rpc_timeout - time.time()) * 1000, 0) if rpc_timeout is not None else None if self._socket.poll(timeout) == 0: raise TimeoutError( f"Timeout on client {self.endpoint}, method name {method_name}, class info: {self}" ) raw_reply, = self._socket.recv_multipart() reply = from_msgpack(raw_reply) _log.debug("Received reply: %s", reply) # there's a possibility that the socket will have some leftover replies from a previous # request on it if that .call() was cancelled or timed out. Therefore, we need to discard replies that # don't match the request just like in the call_async case. if reply.id == request.id: break else: _log.debug('Discarding reply: %s', reply) for warning in reply.warnings: warn(f"{warning.kind}: {warning.body}") if isinstance(reply, RPCError): raise utils.RPCError(reply.error) else: return reply.result