Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
RLock: use single parking_lot level, track recursion manually
Instead of calling lock()/unlock() N times for recursion depth N,
keep parking_lot at 1 level and manage the count ourselves.
This makes acquire/release O(1) and matches CPython's
_PyRecursiveMutex approach (lock once + set level directly).
  • Loading branch information
youknowone committed Mar 5, 2026
commit 8683977378340ee67501bb85d997b8407e440aaa
3 changes: 0 additions & 3 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -2141,9 +2141,6 @@ def __init__(self, a, *, b) -> None:
CustomRLock(1, b=2)
self.assertEqual(warnings_log, [])

def test_release_save_unacquired(self):
return super().test_release_save_unacquired()

@unittest.skip('TODO: RUSTPYTHON; flaky test')
def test_different_thread(self):
return super().test_different_thread()
Expand Down
30 changes: 16 additions & 14 deletions crates/vm/src/stdlib/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,16 @@ pub(crate) mod _thread {
#[pymethod(name = "acquire_lock")]
#[pymethod(name = "__enter__")]
fn acquire(&self, args: AcquireArgs, vm: &VirtualMachine) -> PyResult<bool> {
let result = acquire_lock_impl!(&self.mu, args, vm)?;
if result {
if self.mu.is_owned_by_current_thread() {
// Re-entrant acquisition: just increment our count.
// parking_lot stays at 1 level; we track recursion ourselves.
self.count
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
return Ok(true);
}
let result = acquire_lock_impl!(&self.mu, args, vm)?;
if result {
self.count.store(1, core::sync::atomic::Ordering::Relaxed);
}
Ok(result)
}
Expand All @@ -234,13 +240,13 @@ pub(crate) mod _thread {
if !self.mu.is_owned_by_current_thread() {
return Err(vm.new_runtime_error("cannot release un-acquired lock"));
}
debug_assert!(
self.count.load(core::sync::atomic::Ordering::Relaxed) > 0,
"RLock count underflow"
);
self.count
let prev = self
.count
.fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
unsafe { self.mu.unlock() };
debug_assert!(prev > 0, "RLock count underflow");
if prev == 1 {
unsafe { self.mu.unlock() };
}
Ok(())
}

Expand Down Expand Up @@ -285,9 +291,7 @@ pub(crate) mod _thread {
}
let count = self.count.swap(0, core::sync::atomic::Ordering::Relaxed);
debug_assert!(count > 0, "RLock count underflow");
for _ in 0..count {
unsafe { self.mu.unlock() };
}
unsafe { self.mu.unlock() };
Ok((count, current_thread_id()))
}

Expand All @@ -303,9 +307,7 @@ pub(crate) mod _thread {
if count == 0 {
return Ok(());
}
for _ in 0..count {
self.mu.lock();
}
self.mu.lock();
self.count
.store(count, core::sync::atomic::Ordering::Relaxed);
Ok(())
Expand Down