Skip to content

ib_async.sleep should not create a new async loop to run asyncio.sleep() #103

@GonzRon

Description

@GonzRon
  self._ib_client.sleep(2)
    ~~~~~~~~~~~~~~~~~~~~~^^^
  File "/lib/python3.13/site-packages/ib_async/util.py", line 405, in sleep
    run(asyncio.sleep(secs))
    ~~~^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.13/site-packages/ib_async/util.py", line 360, in run
    result = loop.run_until_complete(task)
  File "/usr/lib64/python3.13/asyncio/base_events.py", line 696, in run_until_complete
    self._check_running()
    ~~~~~~~~~~~~~~~~~~~^^
      File "/usr/lib64/python3.13/asyncio/base_events.py", line 632, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

Disclaimer: Perhaps it is my fault for using the builtin sleep function - maybe I did that a long time ago when I was first learning ib_async and asyncio in general. Perhaps, it is not intended to be used in async contexts, or when trying to use ib_async to manage the event loop.

On with the proposed description - but again, perhaps I am wrong here so take it with a grain of salt:

This library should not use a loop to run asyncio.sleep(). That's because you can cause an event loop error that's difficult to determine the root cause for in large async environments where many components are using the same event loop.

I actually think the problem is in util.py line 484

def getLoop():
    """Get the asyncio event loop for the current thread."""
    return asyncio.get_event_loop_policy().get_event_loop()

gpt analysis:

Given the function's intended behavior, it should return the existing running event loop if one is active. However, the error indicates that the code is attempting to run the event loop again while it's already active. Here's why this might be happening:

  1. Mixing Synchronous and Asynchronous Code
    Your run function in ib_async/util.py is designed to handle both synchronous and asynchronous contexts. Here's the problematic part:
> def run(*awaitables: Awaitable, timeout: Optional[float] = None):
>     loop = getLoop()
>     # ...
>     result = loop.run_until_complete(task)
>     # ...

When run is called from within an asynchronous context (i.e., when the event loop is already running), loop.run_until_complete(task) attempts to start the event loop again, leading to the RuntimeError.

  1. The sleep Function's Implementation
    The sleep function is defined as a synchronous function that internally calls run(asyncio.sleep(secs)):
> def sleep(secs: float = 0.02) -> bool:
>     run(asyncio.sleep(secs))
>     return True

When sleep is invoked within an asynchronous function (which is likely the case in your stack trace), it triggers the run function to attempt to start the event loop again, causing the error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions