Skip to content

Commit f2b9d0d

Browse files
author
Kowser
committed
Always use spawn to start multiple process
- Remove `CONDUCTOR_MP_START_METHOD` env var
1 parent a928669 commit f2b9d0d

10 files changed

Lines changed: 7 additions & 56 deletions

File tree

.github/workflows/agent-e2e.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ jobs:
2828
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
2929
AGENTSPAN_SERVER_URL: http://localhost:8080/api
3030
AGENTSPAN_CLI_PATH: ${{ github.workspace }}/agentspan
31-
CONDUCTOR_MP_START_METHOD: fork
3231
steps:
3332
- name: Checkout code
3433
uses: actions/checkout@v4

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Canonical metrics mode: opt-in harmonized metric surface via `WORKER_CANONICAL_METRICS=true` -- [details](METRICS.md#detailed-technical-notes--unreleased)
1313
- `MetricsSettings` gains `clean_directory` and `clean_dead_pids` for opt-in stale `.db` file cleanup (both default to `False`)
14-
- `CONDUCTOR_MP_START_METHOD` env var to control the worker pool's multiprocessing start method
1514

1615
### Changed
1716

18-
- **Multiprocessing start method now defaults to `spawn` on all platforms** (was `fork` everywhere except Windows). `fork` caused silently-restarting `SIGSEGV` worker subprocesses on macOS (exitcode `-11`) and fork-with-held-lock deadlocks on POSIX. Opt back in with `CONDUCTOR_MP_START_METHOD=fork`. Entrypoint scripts must use the standard `if __name__ == "__main__":` guard, and workers must be defined at module level
17+
- **Multiprocessing start method now defaults to `spawn` on all platforms** (was `fork` everywhere except Windows). `fork` caused silently-restarting `SIGSEGV` worker subprocesses on macOS (exitcode `-11`) and fork-with-held-lock deadlocks on POSIX.
1918
- Legacy metrics emit unchanged by default; no env var required
2019
- `metrics_collector.py` is now a compatibility shim; `from conductor.client.telemetry.metrics_collector import MetricsCollector` continues to work
2120

METRICS.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,6 @@ unreleased metrics harmonization work. For a summary, see the project
344344
`.db` files and `clean_dead_pids` (default `False`) to remove only `.db`
345345
files from PIDs that no longer exist. Both are executed by the factory
346346
against the resolved metrics directory.
347-
- `CONDUCTOR_MP_START_METHOD` env var (`spawn` / `fork` / `forkserver`;
348-
default `fork` on POSIX, `spawn` on Windows) to control the worker pool's
349-
multiprocessing start method (motivated by a `prometheus_client` lock-fork
350-
deadlock).
351347
- Harness manifest sets `WORKER_CANONICAL_METRICS=true`; `harness/main.py`
352348
logs which collector is active.
353349

docs/WORKER.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,6 @@ Requirements under `spawn` (standard Python multiprocessing rules):
279279
- `configuration`, `metrics_settings`, and `event_listeners` passed to
280280
`TaskHandler` must be picklable
281281

282-
To opt back into the previous behavior (e.g., a Linux deployment relying on
283-
fork's copy-on-write memory sharing):
284-
285-
```shell
286-
export CONDUCTOR_MP_START_METHOD=fork # spawn (default) | fork | forkserver
287-
```
288-
289282
If a worker process dies from a signal repeatedly at startup, set
290283
`PYTHONFAULTHANDLER=1` to capture the crashing stack trace from the child.
291284

examples/spawn_safety_probe.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
import os
2020
import sys
2121

22-
# Force spawn BEFORE importing any conductor module: task_handler pins the
23-
# start method at import time from this env var.
24-
os.environ["CONDUCTOR_MP_START_METHOD"] = "spawn"
25-
2622
import multiprocessing
2723
import pickle
2824

src/conductor/ai/agents/runtime/_worker_entries.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@
4040
_MAX_UNWRAP_DEPTH = 32
4141

4242
_REMEDIES = (
43-
"Define the callable at module level (importable by qualified name), "
44-
"or run with CONDUCTOR_MP_START_METHOD=fork (compatibility mode; see the "
45-
"start-method notes in conductor.client.automator.task_handler)."
43+
"Define the callable at module level (importable by qualified name)."
4644
)
4745

4846

src/conductor/client/automator/task_handler.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -38,33 +38,7 @@
3838
_mp_fork_set = False
3939
if not _mp_fork_set:
4040
try:
41-
# Default start method: "spawn" on every platform.
42-
#
43-
# fork() is fundamentally unsafe in a process that holds native
44-
# framework state or non-main threads:
45-
# - macOS: Apple frameworks (CFNetwork, Security, libdispatch) may
46-
# SIGSEGV in forked children, producing silently-restarting worker
47-
# processes with exitcode=-11. CPython switched its own macOS
48-
# default to spawn in 3.8 for the same reason (bpo-33725).
49-
# - all POSIX: forking while another thread holds a lock (e.g. the
50-
# prometheus_client module-level lock, or the TaskHandler monitor
51-
# thread restarting a worker) leaves that lock permanently held in
52-
# the child -> silent deadlock.
53-
# Workers (including the @worker_task decorator path) are pickle-safe,
54-
# so spawn works everywhere. Deployments that rely on fork's
55-
# copy-on-write inheritance can opt back in with
56-
# CONDUCTOR_MP_START_METHOD=fork (re-exposing the hazards above).
57-
# NOTE: spawn requires the standard `if __name__ == "__main__":` guard
58-
# in the entrypoint script.
59-
_default_method = "spawn"
60-
_method = os.environ.get("CONDUCTOR_MP_START_METHOD", "").strip().lower() or _default_method
61-
if _method not in _VALID_MP_START_METHODS:
62-
logger.warning(
63-
"Ignoring invalid CONDUCTOR_MP_START_METHOD=%r; falling back to %r",
64-
_method, _default_method,
65-
)
66-
_method = _default_method
67-
set_start_method(_method)
41+
set_start_method("spawn")
6842
_mp_fork_set = True
6943
except Exception as e:
7044
logger.info("error when setting multiprocessing.set_start_method - maybe the context is set %s", e.args)
@@ -498,10 +472,7 @@ def __check_and_restart_processes(self) -> None:
498472
logger.warning(
499473
"Worker process %s was killed by signal %s. If this "
500474
"repeats on every restart, the process is likely "
501-
"crashing at startup: use the 'spawn' start method "
502-
"(CONDUCTOR_MP_START_METHOD=spawn, the default) and "
503-
"set PYTHONFAULTHANDLER=1 to capture the crashing "
504-
"stack trace.",
475+
"crashing at startup. set PYTHONFAULTHANDLER=1 to capture the crashing stack trace.",
505476
worker_name, -exitcode,
506477
)
507478
if not self.restart_on_failure:

tests/unit/ai/test_worker_entries.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ def test_transfer_entries_pickle(self):
230230

231231
@pytest.fixture
232232
def force_spawn_probe(monkeypatch):
233-
"""Pin the probe's start-method check to 'spawn' so these tests don't
234-
depend on the ambient CONDUCTOR_MP_START_METHOD (hermeticity)."""
233+
"""Pin the probe's start-method check to 'spawn'."""
235234
monkeypatch.setattr(
236235
we.multiprocessing, "get_start_method", lambda allow_none=True: "spawn"
237236
)

tests/unit/resources/spawn_worker_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Regression tests for GitHub issues #264 / #271:
99
@worker_task workers were unpicklable ("cannot pickle '_thread.lock' object" /
1010
"it's not the same object as module.name"), so TaskHandler could not start
11-
worker subprocesses with CONDUCTOR_MP_START_METHOD=spawn (the only safe start
11+
worker subprocesses with set_start_method("spawn") (the only safe start
1212
method on macOS).
1313
"""
1414
from conductor.client.http.models.task import Task

tests/unit/worker/test_worker_spawn_safety.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class TestWorkerInRealSpawnChild(unittest.TestCase):
173173
"""The definitive regression test: transfer a @worker_task Worker into a
174174
real 'spawn' child process (fresh interpreter, re-imports modules) and
175175
execute a task there. This is exactly what TaskHandler does when
176-
CONDUCTOR_MP_START_METHOD=spawn (default), and exactly what failed in
176+
set_start_method("spawn") (default), and exactly what failed in
177177
issues #264/#271."""
178178

179179
def test_worker_executes_in_spawn_child_process(self):

0 commit comments

Comments
 (0)