Skip to content

grass.pygrass: Pin fork start method for RPC servers on Python 3.14#7724

Open
petrasovaa wants to merge 1 commit into
OSGeo:mainfrom
petrasovaa:fix-python-3.14-temporal
Open

grass.pygrass: Pin fork start method for RPC servers on Python 3.14#7724
petrasovaa wants to merge 1 commit into
OSGeo:mainfrom
petrasovaa:fix-python-3.14-temporal

Conversation

@petrasovaa

Copy link
Copy Markdown
Contributor

Problem

Running temporal tools under Python 3.14 fails, e.g. t.list strds:

    Exception in thread Thread-1 (thread_checker):
    ...
    File ".../grass/temporal/c_libraries_interface.py", line 1480, in start_server
        self.server.start()
    ...
    File ".../multiprocessing/forkserver.py", line 94, in connect_to_new_process
        client.connect(self._forkserver_address)
    FileNotFoundError: [Errno 2] No such file or directory
    resource_tracker: There appear to be 1 leaked semaphore objects to clean up

Reported by @veroandreo (WSL, Python 3.14).

Cause

Python 3.14 changed the default multiprocessing start method from fork to forkserver on every platform except macOS
(python/cpython#84559).

GRASS's RPC servers were relying on the implicit fork default. Their worker processes run libgis through ctypes and depend on fork semantics: they inherit the already-initialized C library state and the live GRASS session environment (GISRC, etc.) from the parent. Under forkserver, the worker is forked from an early, clean forkserver process that never had the GRASS runtime set up, and the forkserver control socket gets torn down as the session exits — producing the FileNotFoundError in the checker thread and the leaked-semaphore warning.

Fix

This targeted fix pins fork to restore prior behavior. A longer-term move to spawn is worth evaluating separately...

Pin fork in one shared place and reuse it. The start_server pattern is duplicated across three implementations that all share RPCServerBase:

  • python/grass/pygrass/rpc/base.py — add a module-level MP_CONTEXT that selects fork where it was the previous default, and use it in start_server.
  • python/grass/pygrass/rpc/__init__.py (DataProvider) and python/grass/temporal/c_libraries_interface.py (CLibrariesInterface) — import MP_CONTEXT and use it in their overridden start_server.
if sys.platform != "darwin" and "fork" in mp.get_all_start_methods():
    MP_CONTEXT = mp.get_context("fork")
else:
    MP_CONTEXT = mp.get_context()

macOS was already on spawn (since 3.8) and Windows has no fork, so the change is a deliberate no-op on those platforms and on Python <= 3.13, where fork was already the default.

Testing

Tested with @veroandreo and locally on linux with Python 3.12, worked fine.

Python 3.14 changed the default multiprocessing start method from fork
to forkserver on all platforms except macOS (python/cpython#84559).
The RPC servers in grass.pygrass.rpc and grass.temporal rely on fork:
their worker processes call libgis via ctypes and must inherit the
initialized library state and the GRASS session environment from the
parent. Under forkserver the worker is forked from an early, clean
process without the GRASS runtime, and the forkserver socket teardown
raises FileNotFoundError in the server-checker thread.

Define a shared multiprocessing context in grass.pygrass.rpc.base that
pins fork where it was the previous default, and reuse it in the three
start_server implementations. macOS (already spawn) and Windows (no
fork) keep their default start method, so the change is a no-op there
and on Python <= 3.13.

Root-caused and drafted with the help of Claude (Opus 4.8).
@petrasovaa petrasovaa added the bug Something isn't working label Jul 15, 2026
@github-actions github-actions Bot added Python Related code is in Python libraries labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working libraries Python Related code is in Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant