Skip to content

Unable to cancel asynchronous task while client is reconnecting #1333

Closed
@maxhlc

Description

IMPORTANT: If you have a question, or you are not sure if you have found a bug in this package, then you are in the wrong place. Hit back in your web browser, and then open a GitHub Discussion instead. Likewise, if you are unable to provide the information requested below, open a discussion to troubleshoot your issue.

Describe the bug

I am using the library to connect to multiple Socket.IO servers at the same time within a single process using the asynchronous version of the client. The code is designed to reconnect to these servers automatically as they are not guaranteed to be online throughout the entire lifetime of the process. I pass the retry=True flag during the initial call to AsyncClient.connect so that each client continues to attempt to reconnect.

The asynchronous clients are initialised with handle_sigint=False so that I can handle signals myself. When SIGINT is caught by my code, I am cancelling the asyncio tasks corresponding to the various clients. I have noticed that attempting to kill an AsyncClient task fails if it is within its reconnection loop. The following behaviour does not affect clients that are connected when the corresponding asyncio task is cancelled.

I believe that a try-catch statement in the AsyncClient._handle_reconnect method is causing the issue. This statement, within the reconnection loop at lines 470-488, ignores the asyncio.CancelledError exception raised when cancelling a task. The task never stops, therefore the process cannot finish without forcefully stopping the process outright with SIGKILL.

When I remove asyncio.CancelledError from the list of ignored exceptions, the task cancels successfully, allowing me to stop and close the event loop and exit the process.

I would be interested to see if this behaviour is reproducible by others or whether my implemention is wrong and the try-except block is trying to guard against a different issue.

To Reproduce
Steps to reproduce the behavior:

  1. Create an AsyncClient
  2. Try to connect to an unavailable Socket.IO instance with connection retry enabled
  3. Try to cancel the asynchronous task of the client
  4. The asyncio.CancelledError exception is ignored and the client continues to attempt reconnection

Below is a small example to reproduce the issue. It attempts to stop the client after 5 seconds and will fail without any modification to the reconnection loop code.

NOTE: this code will not stop by itself. You may need to send SIGKILL manually to stop it.

# Standard imports
import asyncio

# Third-party imports
from socketio import AsyncClient


async def kill():
    # Print sleep message
    print("Sleeping for 5 seconds before attempting to stop")

    # Wait for some time before cancelling all tasks
    await asyncio.sleep(5)

    # Print attempt message
    print("Attempting to cancel tasks")

    # Get tasks to cancel
    tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]

    # Cancel tasks
    [task.cancel() for task in tasks]

    # Wait for tasks to finish
    await asyncio.gather(*tasks, return_exceptions=True)

    # Print cancellation message
    print("Tasks cancelled")

    # Stop loop
    loop.stop()


async def main():
    # Declare URL for nonexistent Socket.IO server
    url = "localhost:1234"

    # Declare client
    client = AsyncClient()

    # Try to connect
    await client.connect(url, retry=True)


if __name__ == "__main__":
    # Get event loop
    loop = asyncio.get_event_loop()

    # Add main task
    mainTask = loop.create_task(main())

    # Add kill task
    killTask = loop.create_task(kill())

    try:
        # Keep running loop
        loop.run_forever()
    finally:
        # Close loop
        loop.close()

Expected behavior
During the reconnection loop, asyncio.CancelledError exceptions should not be ignored so that the task can be cancelled when the client is attempting to reconnect.

Logs
N/A

Additional context
N/A

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions