Race condition when connecting with the same websockets transport twice at the same time
See original GitHub issueHi @leszekhanusz ,
First of all, thanks again for implementing the subscription part, that’s great!
I still get some issues to set up multiple subscriptions and I’m not sure how to solve this. Consider taking your example: https://github.com/graphql-python/gql/blame/master/README.md#L318-L342
Can you confirm me that about subscriptions the asyncio.create_task(...)
will immediately run the function in another thread, and that all the await taskX
is to remain the program blocking until each task finishes?
On my side even with your example I get this random error (it’s not immediate, sometimes after 1 second, sometimes 10…):
RuntimeError: cannot call recv while another coroutine is already waiting for the next message
The message is pretty explicit but I don’t understand how to bypass this 😢
If you have any idea 👍
Thank you,
EDIT: that’s weird because sometimes without modifying the code, the process can run more than 5 minutes without having this error…
EDIT2: Note that sometimes I also get this error about the subscribe(...)
method
async for r in self.ws_client.subscribe(subscriptions['scanProbesRequested']):
TypeError: 'async for' requires an object with __aiter__ method, got generator
EDIT3: If I use a different way of doing async (with the same library)
try:
loop = asyncio.get_event_loop()
task3 = loop.create_task(execute_subscription1())
task4 = loop.create_task(execute_subscription2())
loop.run_forever()
it works without any error. That’s really strange…
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
Thanks for your answer @leszekhanusz , I succeeded in making it working, below the before/after cases:
Old way with the issue
New way that works
Conclusion/Differences
The only difference is that I “factorize” the definition of the
session
variable and by doing this there is no longer conflict withrecv()
.At first looking it seems the code should behave the same:
session
being kind of an async alias… but behind the hood I don’t know howasyncio
library deal with that and it seems to be the origin of the conflict.Does it seems right to you now?
Another quick question, I would like to force the auto-reconnect if the connection closes or something wrong happens. Do I have to deal with it manually with another
for ...
above theasync for subscribe()
but also by wrapping the websocket client to reconnect?I see you specified a
connect_args
that is merged to params before giving it towebsockets.connect()
(https://websockets.readthedocs.io/en/stable/api.html#module-websockets.client) but according to their documentation https://github.com/aaugustin/websockets/blob/master/docs/faq.rst#how-do-i-create-channels-or-topics and https://github.com/aaugustin/websockets/issues/414 it doesn’t seem there is something implemented on their side.If you already have an example about the reconnection with the GQL client, it would be really appreciated!
Thank you,
EDIT: I close the issue since it’s solved. If you have any advice about reconnection, I’m still interested to know more about it 😃
You figured it out.
The problem was that you used
async with client as session:
twice in parallel in the first version. This would cause the code to try to connect twice using the same transport. This is normally not allowed and should raise the ExceptionTransportAlreadyConnected
But because of a race condition in the websockets transport it tried to connect twice at the same time… This is a small bug with the transport.
Regarding the retries, I use the backoff module which allows to add a decorator to an async function. This will ensure that if a problem happens, it will retry but with a delay which is getting longer for each retry. I plan to add documentation about this:
Something like this:
EDIT: Note that you can use asyncio.gather now if you want to reduce one line 😃