You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem: Some RPC calls might themselves take a long time to process on the server side. It would be nice if the client could dispatch multiple calls concurrently without the assumption that responses will come back from the server in the order they were sent, and then match incoming responses to the correct requests.
Here's a prototype I came up with using websockets. It does a few things a bit hackishly in order to be able to easily subclass WebsocketClient, and doesn't yet support batch requests. But I think this functionality could easily be implemented directly in AsyncClient:
importasyncio, websockets, jsonfromjsonrpcclient.clients.websockets_clientimportWebSocketsClientfromjsonrpcclient.responseimportResponseclassWebSocketsMultiClient(WebSocketsClient):
def__init__(self, socket, *args, **kwargs):
super().__init__(socket, *args, **kwargs)
self.pending_responses= {}
self.receiving=Falsedef__enter__(self):
self.receiving=asyncio.ensure_future(self.receive_responses())
returnselfdef__exit__(self, *args):
self.receiving.cancel()
asyncdefreceive_responses(self):
whileTrue:
response_text=awaitself.socket.recv()
try:
# Not a proper JSON-RPC response so we just ignore it# since during this mode all messages received from the# socket should be RPC responses# NOTE: We can avoid having to re-parse the response if this functionality# were built directly into AsyncClient.send, for example.response=json.loads(response_text)
response_id=response['id']
queue=self.pending_responses[response_id]
except (json.JSONDecodeError, KeyError):
continueawaitqueue.put(response_text)
asyncdefsend(self, request, **kwargs):
# Override AsyncClient.send to also pass through the request's ID to send_messagekwargs['request_id'] =request.get('id', None)
returnawaitsuper().send(request, **kwargs)
asyncdefsend_message(self, request, response_expected, request_id=None, **kwargs):
ifresponse_expected:
queue=self.pending_responses[request_id] =asyncio.Queue()
awaitself.socket.send(request)
ifresponse_expected:
# As a sanity measure, wait for both the receive_responses task and# the queue. If the receive_responses task returns first that# typically means an error occurred (e.g. the websocket was closed)# If the completed task was receive_responses, when we call# result() it will raise any exception that occurred.done, pending=awaitasyncio.wait([queue.get(), self.receiving],
return_when=asyncio.FIRST_COMPLETED)
fortaskindone:
# Raises an exception if task is self.receivingresult=task.result()
iftaskisnotself.receiving:
response=resultdelself.pending_responses[request_id]
returnResponse(response)
else:
returnResponse('')
This is used like:
withWebSocketsMultiClient(websocket):
# Make RPC requests here
While in the context manager of the client, the client handles all incoming messages in the receive_responses loop and adds them to a queue for each pending response. Messages that aren't expected RPC responses are ignored (TODO: One could easily register a fallback handler for this case as well.)
pinging with message: 7
pinging with message: 1
pinging with message: 2
pinging with message: 6
pinging with message: 4
pinging with message: 3
pinging with message: 0
pinging with message: 5
pinging with message: 9
pinging with message: 8
response from ping 1: pong 1 after waiting 1 seconds
response from ping 5: pong 5 after waiting 1 seconds
response from ping 8: pong 8 after waiting 6 seconds
response from ping 0: pong 0 after waiting 8 seconds
response from ping 7: pong 7 after waiting 12 seconds
response from ping 4: pong 4 after waiting 18 seconds
response from ping 3: pong 3 after waiting 18 seconds
response from ping 9: pong 9 after waiting 23 seconds
response from ping 2: pong 2 after waiting 29 seconds
response from ping 6: pong 6 after waiting 29 seconds
I think this is a valuable use-case and I was surprised I couldn't easily find much else like it already existing, at least for Python.
Update: Fixed an issue where if an error occurred in receive_responses, send_message could block forever waiting for an item on the queue that never arrives. When awaiting queue.get() always check for errors on the receive_responses task as well (e.g. if the websocket connection closes before the response is received).
The text was updated successfully, but these errors were encountered:
Problem: Some RPC calls might themselves take a long time to process on the server side. It would be nice if the client could dispatch multiple calls concurrently without the assumption that responses will come back from the server in the order they were sent, and then match incoming responses to the correct requests.
Here's a prototype I came up with using websockets. It does a few things a bit hackishly in order to be able to easily subclass
WebsocketClient
, and doesn't yet support batch requests. But I think this functionality could easily be implemented directly inAsyncClient
:This is used like:
While in the context manager of the client, the client handles all incoming messages in the
receive_responses
loop and adds them to a queue for each pending response. Messages that aren't expected RPC responses are ignored (TODO: One could easily register a fallback handler for this case as well.)Here's a full client example:
And a corresponding server implementation that can handle multiple ongoing RPC calls:
Some sample output from the client side:
I think this is a valuable use-case and I was surprised I couldn't easily find much else like it already existing, at least for Python.
Update: Fixed an issue where if an error occurred in
receive_responses
,send_message
could block forever waiting for an item on the queue that never arrives. When awaitingqueue.get()
always check for errors on thereceive_responses
task as well (e.g. if the websocket connection closes before the response is received).The text was updated successfully, but these errors were encountered: