@@ -1048,6 +1048,73 @@ def test_file_lock_times_out_when_held(tmp_path):
10481048 pass
10491049
10501050
1051+ class _Clock :
1052+ """Stands in for the lock's `time`: only its own sleeps move it, so a deadline falls on a known attempt."""
1053+
1054+ def __init__ (self ):
1055+ self .now = 0.0
1056+
1057+ def monotonic (self ):
1058+ return self .now
1059+
1060+ def sleep (self , seconds ):
1061+ self .now += seconds
1062+
1063+
1064+ class TestALockFileBeingDeleted :
1065+ """Windows answers creating a lock file its last holder is still deleting with access denied.
1066+
1067+ Read as a failure, it took a snapshot write down under xdist whenever two workers met on one key:
1068+ three acquisitions in 1600 across four processes, measured on Windows.
1069+ """
1070+
1071+ @staticmethod
1072+ def _refusing (monkeypatch , times ):
1073+ """`os.open` refusing the lock *times* times as access denied, then creating it."""
1074+ calls = []
1075+ real = os .open
1076+
1077+ def refusing (path , flags , * mode ):
1078+ calls .append (path )
1079+ if len (calls ) <= times :
1080+ raise PermissionError (13 , "Permission denied" , path )
1081+ return real (path , flags , * mode )
1082+
1083+ monkeypatch .setattr (os , "open" , refusing )
1084+ return calls
1085+
1086+ @pytest .fixture
1087+ def clock (self , monkeypatch ):
1088+ clock = _Clock ()
1089+ monkeypatch .setattr (_snapshot , "time" , clock )
1090+ return clock
1091+
1092+ def test_windows_waits_for_it (self , tmp_path , monkeypatch , clock ):
1093+ monkeypatch .setattr (sys , "platform" , "win32" )
1094+ calls = self ._refusing (monkeypatch , times = 2 )
1095+ with _file_lock (str (tmp_path / "data" ), poll = 0.25 ):
1096+ pass
1097+ assert_that (calls ).is_length (3 )
1098+ assert_that (clock .now ).is_equal_to (0.5 )
1099+
1100+ def test_windows_names_a_refusal_that_outlasts_the_wait (self , tmp_path , monkeypatch , clock ):
1101+ """A directory it may not write to is not a lock being released, and says so rather than timing out."""
1102+ monkeypatch .setattr (sys , "platform" , "win32" )
1103+ calls = self ._refusing (monkeypatch , times = 10_000 )
1104+ with pytest .raises (PermissionError ), _file_lock (str (tmp_path / "data" ), timeout = 1.0 , poll = 0.25 ):
1105+ pass
1106+ assert_that (calls ).is_length (5 )
1107+ assert_that (clock .now ).is_equal_to (1.0 )
1108+
1109+ @pytest .mark .parametrize ("platform" , ["linux" , "darwin" ])
1110+ def test_elsewhere_access_denied_is_what_it_says (self , tmp_path , monkeypatch , platform ):
1111+ monkeypatch .setattr (sys , "platform" , platform )
1112+ calls = self ._refusing (monkeypatch , times = 1 )
1113+ with pytest .raises (PermissionError ), _file_lock (str (tmp_path / "data" ), poll = 0.001 ):
1114+ pass
1115+ assert_that (calls ).is_length (1 )
1116+
1117+
10511118def test_file_lock_serializes_concurrent_writes (tmp_path ):
10521119 target = str (tmp_path / "shared.json" )
10531120 with open (target , "w" ) as fp :
0 commit comments