Skip to content

Commit 20612be

Browse files
committed
Fix AsyncRunner failure on Python 3.14 due to asyncio.get_event_loop() removal
In Python 3.14, asyncio.get_event_loop() raises RuntimeError when no event loop is running. Replace with a version-agnostic approach that: 1. Tries asyncio.get_running_loop() first 2. Falls back to creating a new event loop if none exists This works on Python 3.7+ and handles all cases correctly. Fixes #489
1 parent be769d8 commit 20612be

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

adaptive/runner.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,18 @@ def __init__(
626626
raise_if_retries_exceeded=raise_if_retries_exceeded,
627627
allow_running_forever=True,
628628
)
629-
self.ioloop = ioloop or asyncio.get_event_loop()
629+
if ioloop is not None:
630+
self.ioloop = ioloop
631+
else:
632+
try:
633+
self.ioloop = asyncio.get_running_loop()
634+
except RuntimeError:
635+
# No running event loop exists (e.g., running outside of async context).
636+
# Create a new event loop. This is needed for Python 3.10+ where
637+
# asyncio.get_event_loop() is deprecated when no loop is running,
638+
# and Python 3.14+ where it raises RuntimeError.
639+
self.ioloop = asyncio.new_event_loop()
640+
asyncio.set_event_loop(self.ioloop)
630641

631642
# When the learned function is 'async def', we run it
632643
# directly on the event loop, and not in the executor.

adaptive/tests/test_runner.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import platform
23
import sys
34
import time
@@ -263,3 +264,35 @@ def counting_ask(self, n, tell_pending=True):
263264
finally:
264265
# Restore original method
265266
Learner1D.ask = original_ask
267+
268+
269+
def test_async_runner_without_event_loop():
270+
"""Test that AsyncRunner works when no event loop exists.
271+
272+
In Python 3.10+, asyncio.get_event_loop() was deprecated when no running
273+
event loop exists. In Python 3.12+ it emits a DeprecationWarning, and in
274+
Python 3.14+ it raises a RuntimeError.
275+
276+
This test ensures AsyncRunner properly handles the case when no event
277+
loop exists by creating one.
278+
279+
Regression test for: https://github.com/python-adaptive/adaptive/issues/489
280+
"""
281+
282+
def run_in_thread():
283+
"""Run AsyncRunner in a thread with no event loop."""
284+
# Ensure no event loop exists in this thread
285+
with pytest.raises(RuntimeError, match="no.*event loop"):
286+
asyncio.get_running_loop()
287+
288+
# AsyncRunner should still work - it should create its own event loop
289+
learner = Learner1D(linear, (-1, 1))
290+
runner = AsyncRunner(learner, npoints_goal=10, executor=SequentialExecutor())
291+
runner.block_until_done()
292+
assert learner.npoints >= 10
293+
294+
import threading
295+
296+
thread = threading.Thread(target=run_in_thread)
297+
thread.start()
298+
thread.join()

0 commit comments

Comments
 (0)