From c3141e473186e0b39ec936410f1d44d85ba2e954 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 5 Mar 2026 13:46:53 +0000 Subject: [PATCH] Fix fork+threading deadlock in SWE-Bench image builds Root cause: Commit 2bfcc6c added 'from openhands.sdk import get_logger' to image_utils.py, which auto-initializes RichHandler with locks/threads. When build_utils.py uses ProcessPoolExecutor with fork (default on Linux), it copies the process with Rich logger's locks in their current state, causing child processes to deadlock waiting for locks that will never be released. Solution: Use 'spawn' multiprocessing context instead of 'fork' in build_all_images(), matching the fix in commit 744df225 for evaluation.py. This prevents the fork+threading deadlock by starting fresh Python processes instead of forking processes with inherited thread state. Fixes #476 Co-authored-by: openhands --- benchmarks/utils/build_utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/benchmarks/utils/build_utils.py b/benchmarks/utils/build_utils.py index b4e56b9ad..1fb60657d 100644 --- a/benchmarks/utils/build_utils.py +++ b/benchmarks/utils/build_utils.py @@ -6,6 +6,7 @@ import argparse import contextlib import io +import multiprocessing import os import subprocess import sys @@ -528,7 +529,15 @@ def _chunks(seq: list[str], size: int): ) in_progress: set[str] = set() - with ProcessPoolExecutor(max_workers=max_workers) as ex: + # Use 'spawn' instead of 'fork' to avoid deadlocks when the parent + # process has threads (e.g., from rich.logging.RichHandler). + # With 'fork', child processes inherit copies of locks that may be + # held by threads, causing deadlocks when those locks are needed. + # See: https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods + mp_context = multiprocessing.get_context("spawn") + with ProcessPoolExecutor( + max_workers=max_workers, mp_context=mp_context + ) as ex: futures = {} for base in batch: in_progress.add(base)