diff --git a/marimo/_server/start.py b/marimo/_server/start.py index 8631ff4e249..21a912a2a74 100644 --- a/marimo/_server/start.py +++ b/marimo/_server/start.py @@ -174,6 +174,7 @@ def start( """ Start the server. """ + import packaging.version # Defaults when mcp is enabled if mcp: @@ -296,6 +297,33 @@ def start( initialize_fd_limit(limit=4096) initialize_signals() + # Platform-specific initialization of the event loop policy (Windows + # requires SelectorEventLoop). + initialize_asyncio() + + if packaging.version.Version( + uvicorn.__version__ + ) >= packaging.version.Version("0.36.0"): + # uvicorn 0.36.0 introduced custom event loop policies, and uses a loop + # factory instead of asyncio's global event loop policy (the latter was + # deprecated in Python 3.14). + # + # We have to use a SelectorEventLoop on Windows in particular (because + # we use the add_reader API); Selector is already the default on Unix. + # + # The syntax is awkward: :function + edit_loop_policy = "asyncio:SelectorEventLoop" + else: + # Older versions of uvicorn use the globally configured event + # loop policy + edit_loop_policy = "asyncio" + + # Under uvloop, reading the socket we monitor under add_reader() + # occasionally throws BlockingIOError (errno 11, or errno 35, + # ...). RUN mode no longer uses a socket (it has no IPC) but EDIT + # does, so force asyncio. + loop_policy = edit_loop_policy if mode == SessionMode.EDIT else "auto" + server = uvicorn.Server( uvicorn.Config( app, @@ -323,17 +351,13 @@ def start( # close the websocket if we don't receive a pong after 60 seconds ws_ping_timeout=60, timeout_graceful_shutdown=1, - # Under uvloop, reading the socket we monitor under add_reader() - # occasionally throws BlockingIOError (errno 11, or errno 35, - # ...). RUN mode no longer uses a socket (it has no IPC) but EDIT - # does, so force asyncio. - loop="asyncio" if mode == SessionMode.EDIT else "auto", + # loop can take an arbitrary string but mypy is complaining + # expecting it to be a Literal + loop=loop_policy, # type:ignore[arg-type] ) ) app.state.server = server - initialize_asyncio() - # Execute server startup command if provided if server_startup_command: _execute_startup_command(server_startup_command, session_manager) diff --git a/pyproject.toml b/pyproject.toml index 6432f610907..670628b54db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,7 @@ dependencies = [ "pyyaml>=6.0", # web server # - 0.22.0 introduced timeout-graceful-shutdown, which we use - # - 0.36.0 fails to create new notebooks, see https://github.com/marimo-team/marimo/issues/6453 - "uvicorn>=0.22.0,<0.36.0", + "uvicorn>=0.22.0", # web framework # - 0.29.0 introduced Middleware kwargs, which we use # - starlette 0.36.0 introduced a bug