Skip to content

Commit 049e86f

Browse files
committed
Improve event loop fetching
This wasn't a global problem and only triggered edge cases for people developing with too much (unnecessary?) complexity, but now we don't cache the event loop and instead request the live event loop each time. This is technically about 10x slower than using a cached event loop reader, but the difference is just +20ns slower if we ask python to lookup the active event loop each time vs using it cached on first access forever. Fixes #160 Fixes #186 Fixes #159
1 parent 2e9bcdf commit 049e86f

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

ib_async/connection.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
from eventkit import Event
66

7-
from ib_async.util import getLoop
8-
97

108
class Connection(asyncio.Protocol):
119
"""
@@ -35,7 +33,8 @@ async def connectAsync(self, host, port):
3533
self.disconnect()
3634
await self.disconnected
3735
self.reset()
38-
loop = getLoop()
36+
# Use get_running_loop() directly for optimal performance in async context
37+
loop = asyncio.get_running_loop()
3938
self.transport, _ = await loop.create_connection(lambda: self, host, port)
4039

4140
def disconnect(self):

ib_async/util.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,8 @@ def run(*awaitables: Awaitable, timeout: Optional[float] = None):
361361

362362
if timeout:
363363
future = asyncio.wait_for(future, timeout)
364-
task = asyncio.ensure_future(future)
364+
# Pass loop explicitly to avoid deprecation warnings in Python 3.10+
365+
task = asyncio.ensure_future(future, loop=loop)
365366

366367
def onError(_):
367368
task.cancel()
@@ -492,13 +493,42 @@ def patchAsyncio():
492493
nest_asyncio.apply()
493494

494495

495-
@functools.cache
496496
def getLoop():
497-
"""Get asyncio event loop or create one if it doesn't exist."""
497+
"""
498+
Get asyncio event loop with smart fallback handling.
499+
500+
This function is designed for use in synchronous contexts or when the
501+
execution context is unknown. It will:
502+
1. Try to get the currently running event loop (if in async context)
503+
2. Fall back to getting the current thread's event loop via policy
504+
3. Create a new event loop if none exists or if the existing one is closed
505+
506+
For performance-critical async code paths, prefer using
507+
asyncio.get_running_loop() directly instead of this function.
508+
509+
Note: This function does NOT cache the loop to avoid stale loop bugs
510+
when loops are closed and recreated (e.g., in testing, Jupyter notebooks).
511+
"""
498512
try:
499-
# https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop
513+
# Fast path: we're in an async context (coroutine or callback)
500514
loop = asyncio.get_running_loop()
515+
return loop
501516
except RuntimeError:
517+
pass
518+
519+
# We're in a sync context or no loop is running
520+
# Use the event loop policy to get the loop for this thread
521+
# This avoids deprecation warnings from get_event_loop() in Python 3.10+
522+
try:
523+
loop = asyncio.get_event_loop_policy().get_event_loop()
524+
except RuntimeError:
525+
# No event loop exists for this thread, create one
526+
loop = asyncio.new_event_loop()
527+
asyncio.set_event_loop(loop)
528+
return loop
529+
530+
# Check if the loop we got is closed - if so, create a new one
531+
if loop.is_closed():
502532
loop = asyncio.new_event_loop()
503533
asyncio.set_event_loop(loop)
504534

0 commit comments

Comments
 (0)