From c98e93bc0b5781347309a442b807be890c1815b2 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 11 Mar 2026 00:13:53 +0100 Subject: [PATCH 1/2] fix race condition in test_with_lock, fixes #8810 --- src/borg/testsuite/archiver/lock_cmds_test.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/borg/testsuite/archiver/lock_cmds_test.py b/src/borg/testsuite/archiver/lock_cmds_test.py index 7727e764f..265801c3b 100644 --- a/src/borg/testsuite/archiver/lock_cmds_test.py +++ b/src/borg/testsuite/archiver/lock_cmds_test.py @@ -1,7 +1,6 @@ import os import subprocess import sys -import time from pathlib import Path import pytest @@ -32,16 +31,20 @@ def test_with_lock(tmp_path): command0 = "python3", "-m", "borg", "repo-create", "--encryption=none" # Timings must be adjusted so that command1 keeps running while command2 tries to get the lock, # so that lock acquisition for command2 fails as the test expects it. - lock_wait, execution_time, startup_wait = 2, 4, 1 - assert lock_wait < execution_time - startup_wait - command1 = "python3", "-c", f'import time; print("first command - acquires the lock"); time.sleep({execution_time})' + lock_wait, execution_time = 2, 4 + assert lock_wait < execution_time + command1 = ( + "python3", + "-c", + f'import time; print("first command - acquires the lock", flush=True); time.sleep({execution_time})', + ) command2 = "python3", "-c", 'print("second command - should never get executed")' borgwl = "python3", "-m", "borg", "with-lock", f"--lock-wait={lock_wait}" popen_options = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env) subprocess.run(command0, env=env, check=True, text=True, capture_output=True) assert repo_path.exists() with subprocess.Popen([*borgwl, *command1], **popen_options) as p1: - time.sleep(startup_wait) # Wait until p1 is running + assert "first command" in p1.stdout.readline() # Wait until p1 is running and has acquired the lock # Now try to acquire another lock on the same repository: with subprocess.Popen([*borgwl, *command2], **popen_options) as p2: out, err_out = p2.communicate() @@ -49,7 +52,6 @@ def test_with_lock(tmp_path): assert "Failed to create/acquire the lock" in err_out assert p2.returncode == 73 # LockTimeout: could not acquire the lock, p1 already has it out, err_out = p1.communicate() - assert "first command" in out # command1 was executed and had the lock assert not err_out assert p1.returncode == 0 From 871c89bea0b62868ae50b5d59a61a762f6b3d09a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 11 Mar 2026 11:40:42 +0100 Subject: [PATCH 2/2] Fix intermittent test_with_lock failure (race condition), fixes #8810. test_with_lock previously relied on a hardcoded timeout (`time.sleep(4)`) to ensure the first background command acquired the repository lock before the second command tried to get it. On extremely slow CI runners, this was sometimes too short, allowing the second command to falsely acquire the lock. This commit replaces the arbitrary sleep with true synchronization: - The first command now blocks indefinitely using `sys.stdin.read()`. - The test deterministically waits for lock acquisition by reading `p1.stdout.readline()` which guarantees the lock is held. - After the second command correctly fails, the first command is smoothly unblocked and terminated by passing `input=""` to `p1.communicate()`. --- src/borg/testsuite/archiver/lock_cmds_test.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/borg/testsuite/archiver/lock_cmds_test.py b/src/borg/testsuite/archiver/lock_cmds_test.py index 265801c3b..6ff31a4d2 100644 --- a/src/borg/testsuite/archiver/lock_cmds_test.py +++ b/src/borg/testsuite/archiver/lock_cmds_test.py @@ -31,19 +31,17 @@ def test_with_lock(tmp_path): command0 = "python3", "-m", "borg", "repo-create", "--encryption=none" # Timings must be adjusted so that command1 keeps running while command2 tries to get the lock, # so that lock acquisition for command2 fails as the test expects it. - lock_wait, execution_time = 2, 4 - assert lock_wait < execution_time - command1 = ( - "python3", - "-c", - f'import time; print("first command - acquires the lock", flush=True); time.sleep({execution_time})', - ) + lock_wait = 2 + command1 = ("python3", "-c", 'import sys; print("first command - acquires the lock", flush=True); sys.stdin.read()') command2 = "python3", "-c", 'print("second command - should never get executed")' borgwl = "python3", "-m", "borg", "with-lock", f"--lock-wait={lock_wait}" popen_options = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env) subprocess.run(command0, env=env, check=True, text=True, capture_output=True) assert repo_path.exists() - with subprocess.Popen([*borgwl, *command1], **popen_options) as p1: + + p1_options = popen_options.copy() + p1_options["stdin"] = subprocess.PIPE + with subprocess.Popen([*borgwl, *command1], **p1_options) as p1: assert "first command" in p1.stdout.readline() # Wait until p1 is running and has acquired the lock # Now try to acquire another lock on the same repository: with subprocess.Popen([*borgwl, *command2], **popen_options) as p2: @@ -51,7 +49,7 @@ def test_with_lock(tmp_path): assert "second command" not in out # command2 is "locked out" assert "Failed to create/acquire the lock" in err_out assert p2.returncode == 73 # LockTimeout: could not acquire the lock, p1 already has it - out, err_out = p1.communicate() + out, err_out = p1.communicate(input="") # Unblock command1 and read output assert not err_out assert p1.returncode == 0