slint-ui/slint · error · RuntimeError
run_until_complete's future isn't done
Error message
run_until_complete's future isn't done
What it means
The Python slint package replaces asyncio's loop with SlintEventLoop, which drives the native slint event loop. In run_until_complete, the future normally completes and stops the loop; if the loop stopped early via loop.stop() (which sets stop_run_forever_event and quits the native loop) while the future is still pending, a RuntimeError mimicking CPython's 'Event loop stopped before Future completed' is raised. Quitting because the last window closed (no stop() call) instead returns a None sentinel without raising.
Source
Thrown at api/python/slint/slint/loop.py:187
asyncio.events._set_running_loop(None)
def run_until_complete[T](self, future: typing.Awaitable[T]) -> T | None:
def stop_loop(future: typing.Any) -> None:
self.stop()
future = asyncio.ensure_future(future, loop=self)
future.add_done_callback(stop_loop)
try:
self.run_forever()
finally:
future.remove_done_callback(stop_loop)
if future.done():
return future.result()
else:
if self.stop_run_forever_event.is_set():
raise RuntimeError("run_until_complete's future isn't done", future)
else:
# If the loop was quit for example because the user closed the last window, then
# don't thrown an error but return a None sentinel. The return value of asyncio.run()
# isn't used by slint.run_event_loop() anyway
# TODO: see if we can properly cancel the future by calling cancel() and throwing
# the task cancellation exception.
return None
def _run_forever_setup(self) -> None:
pass
def _run_forever_cleanup(self) -> None:
pass
def stop(self) -> None:
self.stop_run_forever_event.set()
def is_running(self) -> bool:View on GitHub (pinned to a9ea814a58)
Solutions
- Finish or cancel/await your coroutine before anything stops the loop; structure main() so it returns instead of relying on loop.stop().
- Prefer slint.quit_event_loop() (or closing all windows) to end the loop — that path returns the None sentinel instead of raising.
- If third-party code insists on loop.stop(), wrap asyncio.run() in try/except RuntimeError and treat the pending future as aborted.
- Audit signal handlers and shutdown hooks for loop.stop() calls.
Example fix
# before
async def main():
await work()
loop = slint.get_event_loop()
loop.call_later(1, lambda: loop.stop()) # stops early -> RuntimeError
loop.run_until_complete(main())
# after
async def main():
await work()
slint.quit_event_loop() # graceful quit, no RuntimeError
slint.run_event_loop_until_quit() or asyncio.run(main()) Defensive patterns
Strategy: try-catch
Try / catch
try:
asyncio.run(main())
except RuntimeError as e:
if "future isn't done" in str(e):
# loop.stop() fired before main() finished: treat as aborted run
log.warning("event loop stopped before main() completed")
else:
raise Prevention
- Never end the slint loop with loop.stop(); use slint.quit_event_loop() or close all windows.
- Make awaited coroutines return on their own instead of relying on external loop stop.
- Audit third-party asyncio libraries and signal handlers for loop.stop() calls before adopting them.
When it happens
Trigger: Running under asyncio.run()/run_until_complete() when something calls loop.stop() (directly, via asyncio.get_running_loop().stop(), a signal handler, or third-party code assuming a plain asyncio loop) before the awaited coroutine finishes.
Common situations: Porting asyncio code that ends the loop with loop.stop(); KeyboardInterrupt handlers calling loop.stop(); libraries (aiohttp, websockets, test frameworks) that stop the loop on shutdown while a slint-backed coroutine is still pending.
Related errors
- Callback '{name}' in global '{global_name}' cannot be used w
- Callback '{name}' in global '{global_name}' cannot be used w
- Callback '{name}' cannot be used with a callback decorator f
- Callback '{name}' cannot be used with a callback decorator f
- Could not compile {path}
AI-assisted analysis of slint-ui/slint@a9ea814a58 (2026-08-16).
Data as JSON: /api/errors/97692004fc96b4df.
Report an issue: GitHub.