Skip to content

Support for multiple workers in the same process #1809

@lazka

Description

@lazka

Description

When using granian with multiple event loops in one process then mcp errors out. It looks as if mcp can't handle being run multiple times in the same process?

# example.py
import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo", json_response=True)

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
    async with mcp.session_manager.run():
        yield

app = Starlette(
    routes=[
        Mount("/", app=mcp.streamable_http_app()),
    ],
    lifespan=lifespan,
)
uv add granian
uv run --python 3.14t granian --interface asgi --workers 2 example:app --port 8000

error:

Exception ignored while calling deallocator <function BaseEventLoop.__del__ at 0x58eff299fc0>:
Traceback (most recent call last):
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/asyncio/base_events.py", line 760, in __del__
    self.close()
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/asyncio/unix_events.py", line 70, in close
    super().close()
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/asyncio/selector_events.py", line 104, in close
    self._close_self_pipe()
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/asyncio/selector_events.py", line 111, in _close_self_pipe
    self._remove_reader(self._ssock.fileno())
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/asyncio/selector_events.py", line 296, in _remove_reader
    key = self._selector.get_map().get(fd)
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/selectors.py", line 70, in get
    fd = self._selector._fileobj_lookup(fileobj)
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/selectors.py", line 229, in _fileobj_lookup
    return _fileobj_to_fd(fileobj)
  File "/home/lazka/.local/share/uv/python/cpython-3.14.0+freethreaded-linux-x86_64-gnu/lib/python3.14t/selectors.py", line 42, in _fileobj_to_fd
    raise ValueError("Invalid file descriptor: {}".format(fd))
ValueError: Invalid file descriptor: -1

The error goes away if only one worker is used.

Any ideas how to work around this welcome.

References

https://github.com/emmett-framework/granian

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions