Skip to content

fix(runtime): run user code on a Proactor loop on Windows in run and script mode - #10817

Open
BurakErdemci wants to merge 2 commits into
marimo-team:mainfrom
BurakErdemci:fix/windows-proactor-run-script-mode
Open

BurakErdemci wants to merge 2 commits into
marimo-team:mainfrom
BurakErdemci:fix/windows-proactor-run-script-mode

Conversation

@BurakErdemci

@BurakErdemci BurakErdemci commented Sep 13, 2026

Copy link
Copy Markdown

This pull request was authored by a coding agent.

📝 Summary

Closes #9182

On Windows, a cell that awaits asyncio.create_subprocess_exec raises NotImplementedError in marimo run and in script mode (python notebook.py). #9194 fixed edit mode only.

marimo sets WindowsSelectorEventLoopPolicy for the whole process in two places: when marimo._islands._island_generator is imported, and in initialize_asyncio() at server start. A Selector loop on Windows cannot create subprocesses. Edit and IPC kernels run in child processes, and _bootstrap_subprocess switches them back to Proactor. A run-mode kernel runs in a thread inside the server, and AppScriptRunner runs in the caller's process, so both call plain asyncio.run and get a Selector loop.

This PR adds run_on_subprocess_capable_loop to marimo/_utils/asyncio_utils.py. On Windows it runs the coroutine on a ProactorEventLoop that it creates itself, and it leaves the global policy alone because the server and other kernel threads create loops at the same time. It uses asyncio.run(..., loop_factory=...) on 3.12+, asyncio.Runner on 3.11, and a copy of asyncio.run's shutdown steps on 3.10. On other platforms it calls asyncio.run.

  • launch_kernel uses the helper for in-process kernels (run mode and app host). The edit and IPC branches keep their current code.
  • AppScriptRunner uses it for async notebooks.
  • The server keeps its Selector loop. I did not touch the islands import or initialize_asyncio.

test_run_mode_on_windows_does_not_touch_event_loop_policy stated that run mode needs Selector for ConnectionDistributor.add_reader(). Run mode uses QueueDistributor (see NotificationListenerExtension._create_distributor), and ConnectionDistributor.start holds the only add_reader call in the codebase. I replaced that test with one that checks run mode calls the helper and leaves the policy unchanged. If run mode should stay on Selector for a reason I missed, let me know.

Before and after (Windows 11, Python 3.13, a notebook with one async cell that runs python -c "print('ok')"):

Mode Before After
python notebook.py NotImplementedError on _WindowsSelectorEventLoop prints ok on ProactorEventLoop
marimo run notebook.py NotImplementedError prints ok

Tests

  • tests/_utils/test_asyncio_utils.py: return value, exception propagation, pending task cancellation, closed loop, RuntimeError inside a running loop, and a Windows test that spawns a subprocess under the Selector policy and checks the policy object stays the same.
  • tests/_ast/test_app.py::TestApp::test_run_async_subprocess (Windows only) fails with NotImplementedError on main and passes with this change.
  • TestLaunchKernelEventLoop: updated run-mode tests for Windows and other platforms.
  • I ran these on Windows with Python 3.10, 3.11 and 3.13. test_session_manager_file_rename fails on Windows on main too, because of the error message text.

Not covered: a synchronous cell that calls asyncio.run(...) in script mode still gets a Selector loop from the policy that the islands import sets. Changing that import-time policy looks like it would affect islands build(), so I left that decision to you.

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

…script mode

On Windows, marimo installs WindowsSelectorEventLoopPolicy process-wide
(at import of the islands generator and at server start) because the
edit-mode ConnectionDistributor needs loop.add_reader(). Selector loops
cannot spawn subprocesses, so asyncio.create_subprocess_exec in a cell
raised NotImplementedError. marimo-team#9194 fixed this for edit/IPC kernels, which
are child processes; run-mode kernels run in a server thread and script
mode runs in the caller's process, so both still inherited Selector.

Add run_on_subprocess_capable_loop, which runs a coroutine on a locally
created ProactorEventLoop on Windows without touching the global policy
(other threads create loops concurrently), and use it for in-process
run-mode kernels and for AppScriptRunner. Other platforms keep plain
asyncio.run. Run mode uses QueueDistributor, which does not use
add_reader, so the kernel loop does not need Selector.

Fixes marimo-team#9182
@vercel

vercel Bot commented Sep 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marimo-docs Ready Ready Preview Sep 13, 2026 2:28pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@BurakErdemci

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@BurakErdemci
BurakErdemci marked this pull request as ready for review September 13, 2026 14:38
@mscolnick
mscolnick requested a review from akshayka September 14, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NotImplementedError: when using asyncio.create_subprocess_exec on Windows

1 participant