Support an explicit environment mapping for multiprocessing spawn
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 77.2k
- フォーク
- 35.9k
- PR マージ指標
- PR 指標を取得中
説明
Bug report
Bug description:
"""Linux spawn/environment race. Use --no-environment-updates as a control."""
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
import multiprocessing as mp
import os
import sys
import threading
import time
def child():
pass
def update_environment(stop):
keys = [f"SPAWN_REPRO_{i}" for i in range(256)]
while not stop.is_set():
for key in keys:
os.environ[key] = "test"
for key in keys:
os.environ.pop(key, None)
time.sleep(0)
def start_process(_):
process = mp.get_context("spawn").Process(target=child)
try:
process.start()
except Exception as error:
process.close()
return error
return process
if __name__ == "__main__":
print(sys.version, flush=True)
# Start the resource tracker before racing, using only the public API.
warmup = mp.get_context("spawn").Process(target=child)
warmup.start()
warmup.join()
warmup.close()
stop = threading.Event()
updater = threading.Thread(target=update_environment, args=(stop,))
if "--no-environment-updates" not in sys.argv:
updater.start()
try:
with ThreadPoolExecutor(32) as executor:
for round_number in range(20):
# Finish all starts before joining: avoid concurrent reaping.
results = list(executor.map(start_process, range(32)))
counts = Counter()
for result in results:
if isinstance(result, Exception):
counts[repr(result)] += 1
else:
result.join()
counts[result.exitcode] += 1
result.close()
print(f"round {round_number + 1}: {dict(counts)}", flush=True)
if counts[0] != 32:
raise SystemExit(1)
finally:
stop.set()
if updater.ident is not None:
updater.join()
if you run python test.py, you will get :
round 1: {0: 32}
round 2: {0: 32}
round 3: {0: 32}
round 4: {0: 31, "BrokenPipeError(32, 'Broken pipe')": 1}
and if you run with --no-environment-updates, the subprocess create successfully.
round 1: {0: 32}
round 2: {0: 32}
round 3: {0: 32}
round 4: {0: 32}
round 5: {0: 32}
round 6: {0: 32}
round 7: {0: 32}
round 8: {0: 32}
round 9: {0: 32}
round 10: {0: 32}
round 11: {0: 32}
round 12: {0: 32}
round 13: {0: 32}
round 14: {0: 32}
round 15: {0: 32}
round 16: {0: 32}
round 17: {0: 32}
round 18: {0: 32}
round 19: {0: 32}
round 20: {0: 32}
In my tests on Linux with CPython 3.14, the multiprocessing "spawn" start method follows a vfork() + execv() path. The child shares the parent’s address space until it successfully executes a new program or terminates. During this interval, other threads in the parent can continue modifying the process environment.
With another thread concurrently modifying os.environ, I observed execv() returning EFAULT, followed by the child exiting with status 255. At the Python level, this can surface as a BrokenPipeError from Process.start(). These observations are consistent with a race between the kernel reading the environment passed to execve() and another thread modifying the underlying global environ array.
The C posix_spawn() API accepts an explicit envp argument, and Python’s subprocess.Popen(env=...) similarly allows callers to supply an environment mapping. Could we consider adding an env parameter to multiprocessing.Process for the "spawn" start method, for example mp.get_context("spawn").Process(target=child, env=child_env)? This would allow callers to provide a stable environment for the child interpreter before exec, without temporarily modifying the process-global environment.
CPython versions tested on:
3.14
Operating systems tested on:
Linux
Linked PRs
- gh-157656
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
まず、提供された再現用の test.py を --no-environment-updates ありとなしで実行し、次にレポートで説明されている multiprocessing spawn Process.start パスを追跡します。spawn プロセスが明示的で安定した環境マッピングを受け取れるようになり、プロセスグローバルな環境更新に依存せずに再現用コードが正常に完了すれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- linux, python
- 領域
- operating-systems
- issue の種類
- 機能追加
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 30/100