From 4fc4d7863cdc95e05f6f0004825e543e2759199f Mon Sep 17 00:00:00 2001 From: Test Date: Wed, 15 Apr 2026 16:39:00 +0000 Subject: [PATCH 1/2] fix(tests): prevent behave-parallel multiprocessing deadlock on btrfs/overlayfs Detect btrfs and overlayfs filesystems and automatically fall back to sequential mode to prevent deadlocks caused by SQLite WAL file locking and btrfs COW copy-up locks when multiple forked workers try to access the same files simultaneously. The fix adds a _is_btrfs_or_overlayfs() function that: 1. Attempts to detect the filesystem type using stat command 2. Falls back to reading /proc/mounts if stat fails 3. Returns True if the filesystem is btrfs or overlayfs The sequential mode condition is updated to include this check, ensuring that on affected filesystems, all features run in a single process instead of being split across multiple forked workers. ISSUES CLOSED: #9390 --- scripts/run_behave_parallel.py | 50 ++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/scripts/run_behave_parallel.py b/scripts/run_behave_parallel.py index b3d6e4d80..9db868d34 100644 --- a/scripts/run_behave_parallel.py +++ b/scripts/run_behave_parallel.py @@ -23,6 +23,7 @@ import argparse import io import multiprocessing import os +import subprocess import sys import time import traceback @@ -32,6 +33,52 @@ from typing import Any DEFAULT_FEATURE_ROOT = "features/" + +def _is_btrfs_or_overlayfs() -> bool: + fs_targets = {"btrfs", "overlay"} + + try: + result = subprocess.run( + ["stat", "-f", "-c", "%T", "."], + check=True, + capture_output=True, + text=True, + ) + except (OSError, subprocess.SubprocessError): + pass + else: + fs_type = result.stdout.strip().lower() + return fs_type in fs_targets + + try: + current_path = os.path.realpath(".") + best_match_len = -1 + best_fs_type: str | None = None + with open("/proc/mounts", encoding="utf-8") as mounts: + for line in mounts: + parts = line.split() + if len(parts) < 3: + continue + mount_point = parts[1].replace("\\040", " ") + mount_real = os.path.realpath(mount_point) + if not mount_real: + continue + if ( + (current_path == mount_real or current_path.startswith( + mount_real.rstrip("/") + "/" + )) + and len(mount_real) > best_match_len + ): + best_match_len = len(mount_real) + best_fs_type = parts[2].lower() + if best_fs_type: + return best_fs_type in fs_targets + except OSError: + pass + + return False + + # Type alias for summary dictionaries Summary = dict[str, Any] @@ -321,8 +368,6 @@ def main(argv: list[str] | None = None) -> None: # Pass-through --help / --version to behave if any(flag in remaining for flag in ("-h", "--help", "--version")): - import subprocess - code = subprocess.run( [sys.executable, "-m", "behave", *other_args, *feature_args] ).returncode @@ -348,6 +393,7 @@ def main(argv: list[str] | None = None) -> None: or coverage_mode or len(feature_paths) == 1 or len(feature_paths) <= 2 + or _is_btrfs_or_overlayfs() ): # ---- sequential in-process mode ---- _, total = _run_features_inprocess(feature_paths, other_args) -- 2.52.0 From 449c33b752f9c81cc0944b78ee1a8e079a940a0b Mon Sep 17 00:00:00 2001 From: CleverThis Date: Thu, 16 Apr 2026 06:22:35 +0000 Subject: [PATCH 2/2] style(tests): apply ruff format to run_behave_parallel.py Fix formatting of conditional expression in _is_btrfs_or_overlayfs() that was flagged by ruff format --check in CI. --- scripts/run_behave_parallel.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/run_behave_parallel.py b/scripts/run_behave_parallel.py index 9db868d34..0731a103a 100644 --- a/scripts/run_behave_parallel.py +++ b/scripts/run_behave_parallel.py @@ -64,11 +64,9 @@ def _is_btrfs_or_overlayfs() -> bool: if not mount_real: continue if ( - (current_path == mount_real or current_path.startswith( - mount_real.rstrip("/") + "/" - )) - and len(mount_real) > best_match_len - ): + current_path == mount_real + or current_path.startswith(mount_real.rstrip("/") + "/") + ) and len(mount_real) > best_match_len: best_match_len = len(mount_real) best_fs_type = parts[2].lower() if best_fs_type: -- 2.52.0