File tree Expand file tree Collapse file tree 3 files changed +37
-4
lines changed Expand file tree Collapse file tree 3 files changed +37
-4
lines changed Original file line number Diff line number Diff line change
1
+ Make ``ReentrantFileLock `` thread-safe and,
2
+ thereby, fix race condition in ``virtualenv.cli_run `` - by :user: `radoering `.
Original file line number Diff line number Diff line change @@ -23,13 +23,16 @@ def __init__(self, lock_file):
23
23
self .thread_safe = RLock ()
24
24
25
25
def acquire (self , timeout = None , poll_interval = 0.05 ):
26
- with self .thread_safe :
27
- if self .count == 0 :
28
- super ().acquire (timeout , poll_interval )
29
- self .count += 1
26
+ if not self .thread_safe .acquire (timeout = - 1 if timeout is None else timeout ):
27
+ raise Timeout (self .lock_file )
28
+ if self .count == 0 :
29
+ super ().acquire (timeout , poll_interval )
30
+ self .count += 1
30
31
31
32
def release (self , force = False ):
32
33
with self .thread_safe :
34
+ if self .count > 0 :
35
+ self .thread_safe .release ()
33
36
if self .count == 1 :
34
37
super ().release (force = force )
35
38
self .count = max (self .count - 1 , 0 )
Original file line number Diff line number Diff line change
1
+ import concurrent .futures
2
+ import traceback
3
+
4
+ import pytest
5
+
6
+ from virtualenv .util .lock import ReentrantFileLock
1
7
from virtualenv .util .subprocess import run_cmd
2
8
3
9
@@ -6,3 +12,25 @@ def test_run_fail(tmp_path):
6
12
assert err
7
13
assert not out
8
14
assert code
15
+
16
+
17
+ def test_reentrant_file_lock_is_thread_safe (tmp_path ):
18
+ lock = ReentrantFileLock (tmp_path )
19
+ target_file = tmp_path / "target"
20
+ target_file .touch ()
21
+
22
+ def recreate_target_file ():
23
+ with lock .lock_for_key ("target" ):
24
+ target_file .unlink ()
25
+ target_file .touch ()
26
+
27
+ with concurrent .futures .ThreadPoolExecutor () as executor :
28
+ tasks = []
29
+ for _ in range (4 ):
30
+ tasks .append (executor .submit (recreate_target_file ))
31
+ concurrent .futures .wait (tasks )
32
+ for task in tasks :
33
+ try :
34
+ task .result ()
35
+ except Exception :
36
+ pytest .fail (traceback .format_exc ())
You can’t perform that action at this time.
0 commit comments