Closed ryanc-bs closed 5 months ago
Thanks for reporting this @ryanc-bs and putting it back in the spotlight. This is a known bug. I believe that, for now, you can set the silence_listener_task_exceptions
kwarg on the WebsocketProviderV2
and catch all Exception
cases in your code block since the listener task may raise a asyncio.exceptions.CancelledError
I believe.
I hope to get to this next on my list if not very soon.
Thanks, apologies if this has already been reported - I could not find anything similar in the open issues currently.
I tried setting the silence_listener_task_exceptions
parameter to True however that does not appear to work well for me as it gets stuck in an error loop:
ERROR:web3.providers.WebsocketProviderV2:Exception caught in listener, error logging and keeping listener background task alive.
error=ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
ERROR:web3.providers.WebsocketProviderV2:Exception caught in listener, error logging and keeping listener background task alive.
error=ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
ERROR:web3.providers.WebsocketProviderV2:Exception caught in listener, error logging and keeping listener background task alive.
error=ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
ERROR:web3.providers.WebsocketProviderV2:Exception caught in listener, error logging and keeping listener background task alive.
error=ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
ERROR:web3.providers.WebsocketProviderV2:Exception caught in listener, error logging and keeping listener background task alive.
error=ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
...
What does seem to work well is using the async context manager and wrapping in a retry loop like:
async def main():
while True:
try:
await subscribe()
except ConnectionClosed:
logging.info("connection closed, retrying")
async def subscribe():
async with AsyncWeb3.persistent_websocket(
WebsocketProviderV2(...)
) as w3:
subscription_id = await w3.eth.subscribe("newHeads")
async for response in w3.ws.process_subscriptions():
block_number = response["result"]["number"]
logging.info("block number: %d", block_number)
It's a little unclear to me why the error handling in this case is different to that of the async iterator, from a quick glance at the source code I could not find where the relevant __aenter__
/ __aiter__
methods are defined exactly.
Also with respect to the asyncio.CancelledError, since that is inheriting from BaseException it cannot be caught by catching Exception instances. Also it is my understanding that this shouldn't be caught without re-raising as per the docs: https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError
Also with respect to the asyncio.CancelledError, since that is inheriting from BaseException it cannot be caught by catching Exception instances.
Yep, that checks out. You'd have to handle that separately.
Also it is my understanding that this shouldn't be caught without re-raising as per the docs: https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError
Correct. In any cases where there isn't a bug, this should be handled appropriately. This is definitely a bug at the moment but I'm glad you have a workaround for now.
Thanks, apologies if this has already been reported - I could not find anything similar in the open issues currently.
It was only reported in the Discord server so this actually helps us keep track of it so thank you! :)
I'll get to this asap 👍🏼
What happened?
I am using the latest stable release - 6.18.0
I'm trying out the AsyncWeb3 class following the usage as described in the docs (https://web3py.readthedocs.io/en/stable/providers.html#usage). I would like to maintain an indefinite subscription, automatically reconnecting where necessary due to connection drops. From my understanding this should be possible via the second example using the
async for w3 in AsyncWeb3.persistent_websocket(...
pattern, however this does not work as expected for me.Using that example, I am able to connect and retrieve some values, however after a short while when receiving a ConnectionClosedError the connection is not automatically retried. Instead, the main asyncio task is cancelled meaning that I am unable to retry by catching
websockets.ConnectionClosed
.Code that produced the error
Full error output
Fill this section in if you know how this could or should be fixed
It seems like the error handling here is problematic: https://github.com/ethereum/web3.py/blob/08665fcd54e0e593ba6d19a884b4ba8c9b5753d0/web3/providers/persistent/websocket.py#L198-L209
Since the listener is run in a separate background task, when it cancels all tasks this prevents the ConnectionClosed error from being caught and handled by reconnecting.
web3 Version
6.18.0
Python Version
3.11.8
Operating System
osx
Output from
pip freeze