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

  1. Finish or cancel/await your coroutine before anything stops the loop; structure main() so it returns instead of relying on loop.stop().
  2. Prefer slint.quit_event_loop() (or closing all windows) to end the loop — that path returns the None sentinel instead of raising.
  3. If third-party code insists on loop.stop(), wrap asyncio.run() in try/except RuntimeError and treat the pending future as aborted.
  4. 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

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


AI-assisted analysis of slint-ui/slint@a9ea814a58 (2026-08-16). Data as JSON: /api/errors/97692004fc96b4df. Report an issue: GitHub.