Skip to content

Commit 42dd9f4

Browse files
committed
Remove Mutex from GdCellInner.
--------- - Use `thread_tracker` to guard GdCellInner. - Implement `InaccessibleGuardBlocking` which block thread upon dropping.
1 parent 2ff388a commit 42dd9f4

File tree

6 files changed

+85
-146
lines changed

6 files changed

+85
-146
lines changed

godot-cell/src/blocking_cell.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use std::pin::Pin;
1111
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
1212
use std::thread;
1313

14-
use crate::blocking_guards::{MutGuardBlocking, RefGuardBlocking};
14+
use crate::blocking_guards::{InaccessibleGuardBlocking, MutGuardBlocking, RefGuardBlocking};
1515
use crate::cell::GdCellInner;
16-
use crate::guards::InaccessibleGuard;
1716

1817
/// Blocking version of [`panicking::GdCell`](crate::panicking::GdCell) for multithreaded usage.
1918
///
2019
/// This version of GdCell blocks the current thread if it does not yet hold references to the cell.
20+
/// Since `GdCellInner` isn't thread-safe by itself, any access to `inner` should be guarded by the `thread_tracker`.
2121
///
2222
/// For more details on when threads are being blocked see [`Self::borrow`] and [`Self::borrow_mut`].
2323
///
@@ -107,6 +107,7 @@ impl<T> GdCellBlocking<T> {
107107
inner_guard,
108108
self.mut_condition.clone(),
109109
self.immut_condition.clone(),
110+
self.thread_tracker.clone(),
110111
))
111112
}
112113

@@ -119,11 +120,14 @@ impl<T> GdCellBlocking<T> {
119120
pub fn make_inaccessible<'cell, 'val>(
120121
&'cell self,
121122
current_ref: &'val mut T,
122-
) -> Result<InaccessibleGuard<'val, T>, Box<dyn Error>>
123+
) -> Result<InaccessibleGuardBlocking<'val, T>, Box<dyn Error>>
123124
where
124125
'cell: 'val,
125126
{
126-
self.inner.as_ref().make_inaccessible(current_ref)
127+
let _tracker_guard = self.thread_tracker.lock().unwrap();
128+
let inner = self.inner.as_ref().make_inaccessible(current_ref)?;
129+
let inaccessible = InaccessibleGuardBlocking::new(inner, self.thread_tracker.clone());
130+
Ok(inaccessible)
127131
}
128132

129133
/// Returns `true` if there are any mutable or shared references, regardless of whether the mutable
@@ -164,6 +168,11 @@ impl<T> GdCellBlocking<T> {
164168
}
165169
}
166170

171+
// SAFETY: `T` is Sync, so we can return references to it on different threads.
172+
// It is also Send, so we can return mutable references to it on different threads.
173+
// Additionally, all internal state is synchronized via a mutex, so we won't have race conditions when trying to use it from multiple threads.
174+
unsafe impl<T: Send + Sync> Sync for GdCellBlocking<T> {}
175+
167176
/// Holds the reference count and the currently mutable thread.
168177
#[derive(Debug)]
169178
pub(crate) struct ThreadTracker {

godot-cell/src/blocking_guards.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::sync::{Arc, Condvar, Mutex};
1111

1212
use crate::blocking_cell::ThreadTracker;
1313
use crate::guards::{MutGuard, RefGuard};
14+
use crate::panicking::InaccessibleGuard;
1415

1516
/// Extended version of [`panicking::RefGuard`](crate::panicking::RefGuard) that tracks which thread a reference belongs to and when it's dropped.
1617
///
@@ -50,7 +51,7 @@ impl<T> Drop for RefGuardBlocking<'_, T> {
5051

5152
state_lock.decrement_current_thread_shared_count();
5253

53-
// SAFETY: guard is dropped exactly once, here.
54+
// SAFETY: guard is dropped exactly once, here, while state is guarded by `_tracker_guard` preventing access from any other thread.
5455
unsafe { ManuallyDrop::drop(&mut self.inner) };
5556

5657
self.mut_condition.notify_one();
@@ -66,18 +67,21 @@ pub struct MutGuardBlocking<'a, T> {
6667
inner: ManuallyDrop<MutGuard<'a, T>>,
6768
mut_condition: Arc<Condvar>,
6869
immut_condition: Arc<Condvar>,
70+
state: Arc<Mutex<ThreadTracker>>,
6971
}
7072

7173
impl<'a, T> MutGuardBlocking<'a, T> {
7274
pub(crate) fn new(
7375
inner: MutGuard<'a, T>,
7476
mut_condition: Arc<Condvar>,
7577
immut_condition: Arc<Condvar>,
78+
state: Arc<Mutex<ThreadTracker>>,
7679
) -> Self {
7780
Self {
7881
inner: ManuallyDrop::new(inner),
7982
immut_condition,
8083
mut_condition,
84+
state,
8185
}
8286
}
8387
}
@@ -98,10 +102,59 @@ impl<T> DerefMut for MutGuardBlocking<'_, T> {
98102

99103
impl<T> Drop for MutGuardBlocking<'_, T> {
100104
fn drop(&mut self) {
101-
// SAFETY: guard is dropped exactly once, here.
105+
let _tracker_guard = self.state.lock().unwrap();
106+
107+
// SAFETY: guard is dropped exactly once, here, while state is guarded by `_tracker_guard` preventing access from any other thread.
102108
unsafe { ManuallyDrop::drop(&mut self.inner) };
103109

104110
self.mut_condition.notify_one();
105111
self.immut_condition.notify_all();
106112
}
107113
}
114+
115+
/// Extended version of [`panicking::InaccessibleGuard`](crate::panicking::InaccessibleGuard) that blocks thread upon dropping.
116+
///
117+
/// See [`panicking::InaccessibleGuard`](crate::panicking::InaccessibleGuard) for more details.
118+
#[derive(Debug)]
119+
pub struct InaccessibleGuardBlocking<'a, T> {
120+
inner: ManuallyDrop<InaccessibleGuard<'a, T>>,
121+
state: Arc<Mutex<ThreadTracker>>,
122+
}
123+
124+
impl<'a, T> InaccessibleGuardBlocking<'a, T> {
125+
pub(crate) fn new(inner: InaccessibleGuard<'a, T>, state: Arc<Mutex<ThreadTracker>>) -> Self {
126+
Self {
127+
inner: ManuallyDrop::new(inner),
128+
state,
129+
}
130+
}
131+
132+
/// Drop self if possible, otherwise returns self again.
133+
///
134+
/// Used currently in the mock-tests, as we need a thread safe way to drop self. Using the normal drop
135+
/// logic may poison state, however it should not cause any UB either way.
136+
///
137+
/// See [`panicking::InaccessibleGuard::try_drop`](crate::panicking::InaccessibleGuard::try_drop) for more details.
138+
pub fn try_drop(self) -> Result<(), ManuallyDrop<Self>> {
139+
let mut manual = ManuallyDrop::new(self);
140+
let can_drop = {
141+
let _guard = manual.state.lock();
142+
manual.inner.can_drop()
143+
};
144+
145+
if !can_drop {
146+
Err(manual)
147+
} else {
148+
unsafe { ManuallyDrop::drop(&mut manual) };
149+
Ok(())
150+
}
151+
}
152+
}
153+
154+
impl<T> Drop for InaccessibleGuardBlocking<'_, T> {
155+
fn drop(&mut self) {
156+
let state_lock = self.state.lock().unwrap();
157+
unsafe { ManuallyDrop::drop(&mut self.inner) };
158+
drop(state_lock)
159+
}
160+
}

godot-cell/src/cell.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ impl<T> GdCell<T> {
7373
///
7474
/// This cell must be pinned to be usable, as it stores self-referential pointers. The [`GdCell`] type abstracts this detail away from
7575
/// the public type.
76-
// TODO: consider not using `Mutex`
76+
///
77+
/// The cell is **not** thread-safe by itself.
7778
#[derive(Debug)]
7879
pub(crate) struct GdCellInner<T> {
7980
/// The mutable state of this cell.
@@ -181,13 +182,7 @@ impl<T> GdCellInner<T> {
181182
}
182183
}
183184

184-
// SAFETY: `T` is Sync, so we can return references to it on different threads.
185-
// It is also Send, so we can return mutable references to it on different threads.
186-
// Additionally, all internal state is synchronized via a mutex, so we won't have race conditions when trying to use it from multiple threads.
187-
unsafe impl<T: Send + Sync> Sync for GdCellInner<T> {}
188-
189-
/// Mutable state of the `GdCell`, bundled together to make it easier to avoid deadlocks when locking the
190-
/// mutex.
185+
/// Mutable state of the `GdCell`.
191186
#[derive(Debug)]
192187
pub(crate) struct CellState<T> {
193188
/// Tracking the borrows this cell has. This ensures relevant invariants are upheld.

godot-cell/src/guards.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,20 @@ impl<'a, T> InaccessibleGuard<'a, T> {
255255
state.pop_ptr(prev_ptr);
256256
}
257257

258+
#[doc(hidden)]
259+
pub fn can_drop(&self) -> bool {
260+
let state = unsafe { self.state.get().as_mut() }.unwrap();
261+
state.borrow_state.may_unset_inaccessible() || state.stack_depth == self.stack_depth
262+
}
263+
258264
/// Drop self if possible, otherwise returns self again.
259265
///
260266
/// Used currently in the mock-tests, as we need a thread safe way to drop self. Using the normal drop
261267
/// logic may poison state, however it should not cause any UB either way.
262268
#[doc(hidden)]
263269
pub fn try_drop(self) -> Result<(), std::mem::ManuallyDrop<Self>> {
264270
let manual = std::mem::ManuallyDrop::new(self);
265-
let state = unsafe { manual.state.get().as_mut() }.unwrap();
266-
if !state.borrow_state.may_unset_inaccessible() || state.stack_depth != manual.stack_depth {
271+
if !manual.can_drop() {
267272
return Err(manual);
268273
}
269274
Self::perform_drop(manual.state, manual.prev_ptr, manual.stack_depth);

godot-cell/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub mod panicking {
4343

4444
pub mod blocking {
4545
pub use crate::blocking_cell::GdCellBlocking as GdCell;
46-
pub use crate::blocking_guards::{MutGuardBlocking as MutGuard, RefGuardBlocking as RefGuard};
47-
pub use crate::guards::InaccessibleGuard;
46+
pub use crate::blocking_guards::{
47+
InaccessibleGuardBlocking as InaccessibleGuard, MutGuardBlocking as MutGuard,
48+
RefGuardBlocking as RefGuard,
49+
};
4850
}

godot-cell/tests/mock/panicking.rs

Lines changed: 3 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
//! A mock implementation of our instance-binding pattern in pure rust.
99
//!
1010
//! Used so we can run miri on this, which we cannot when we are running in itest against Godot.
11+
//!
12+
//! Currently, the panicking `GdCell` is suitable only for single-threaded use. Without `experimental-threads` enabled,
13+
//! godot-rust will block access to bindings from any thread other than the main one.
1114
1215
use std::collections::HashMap;
1316
use std::error::Error;
@@ -55,131 +58,3 @@ fn all_calls_work() {
5558
}
5659
}
5760
}
58-
59-
/// Run each method both from the main thread and a newly created thread.
60-
#[test]
61-
fn calls_different_thread() {
62-
use std::thread;
63-
64-
let instance_id = MyClass::init();
65-
66-
// We're not running in parallel, so it will never fail to increment completely.
67-
for (f, _, expected_increment) in CALLS {
68-
let start = unsafe { get_int(instance_id) };
69-
unsafe {
70-
f(instance_id).unwrap();
71-
72-
assert_id_is(instance_id, start + expected_increment);
73-
}
74-
let start = start + expected_increment;
75-
thread::spawn(move || unsafe { f(instance_id).unwrap() })
76-
.join()
77-
.unwrap();
78-
unsafe {
79-
assert_id_is(instance_id, start + expected_increment);
80-
}
81-
}
82-
}
83-
84-
/// Call each method from different threads, allowing them to run in parallel.
85-
///
86-
/// This may cause borrow failures, we do a best-effort attempt at estimating the value then. We can detect
87-
/// if the first call failed, so then we know the integer was incremented by 0. Otherwise, we at least know
88-
/// the range of values that it can be incremented by.
89-
#[test]
90-
fn calls_parallel() {
91-
use std::thread;
92-
93-
let instance_id = MyClass::init();
94-
let mut handles = Vec::new();
95-
96-
for (f, min_increment, max_increment) in CALLS {
97-
let handle = thread::spawn(move || unsafe {
98-
f(instance_id).map_or((0, 0), |_| (*min_increment, *max_increment))
99-
});
100-
handles.push(handle);
101-
}
102-
103-
let (min_expected, max_expected) = handles
104-
.into_iter()
105-
.map(|handle| handle.join().unwrap())
106-
.reduce(|(curr_min, curr_max), (min, max)| (curr_min + min, curr_max + max))
107-
.unwrap();
108-
109-
unsafe {
110-
assert!(get_int(instance_id) >= min_expected);
111-
assert!(get_int(instance_id) <= max_expected);
112-
}
113-
}
114-
115-
/// Call each method from different threads, allowing them to run in parallel.
116-
///
117-
/// This may cause borrow failures, we do a best-effort attempt at estimating the value then. We can detect
118-
/// if the first call failed, so then we know the integer was incremented by 0. Otherwise, we at least know
119-
/// the range of values that it can be incremented by.
120-
///
121-
/// Runs each method several times in a row. This should reduce the non-determinism that comes from
122-
/// scheduling of threads.
123-
#[test]
124-
fn calls_parallel_many_serial() {
125-
use std::thread;
126-
127-
let instance_id = MyClass::init();
128-
let mut handles = Vec::new();
129-
130-
for (f, min_increment, max_increment) in CALLS {
131-
for _ in 0..10 {
132-
let handle = thread::spawn(move || unsafe {
133-
f(instance_id).map_or((0, 0), |_| (*min_increment, *max_increment))
134-
});
135-
handles.push(handle);
136-
}
137-
}
138-
139-
let (min_expected, max_expected) = handles
140-
.into_iter()
141-
.map(|handle| handle.join().unwrap())
142-
.reduce(|(curr_min, curr_max), (min, max)| (curr_min + min, curr_max + max))
143-
.unwrap();
144-
145-
unsafe {
146-
assert!(get_int(instance_id) >= min_expected);
147-
assert!(get_int(instance_id) <= max_expected);
148-
}
149-
}
150-
151-
/// Call each method from different threads, allowing them to run in parallel.
152-
///
153-
/// This may cause borrow failures, we do a best-effort attempt at estimating the value then. We can detect
154-
/// if the first call failed, so then we know the integer was incremented by 0. Otherwise, we at least know
155-
/// the range of values that it can be incremented by.
156-
///
157-
/// Runs all the tests several times. This is different from [`calls_parallel_many_serial`] as that calls the
158-
/// methods like AAA...BBB...CCC..., whereas this interleaves the methods like ABC...ABC...ABC...
159-
#[test]
160-
fn calls_parallel_many_parallel() {
161-
use std::thread;
162-
163-
let instance_id = MyClass::init();
164-
let mut handles = Vec::new();
165-
166-
for _ in 0..10 {
167-
for (f, min_increment, max_increment) in CALLS {
168-
let handle = thread::spawn(move || unsafe {
169-
f(instance_id).map_or((0, 0), |_| (*min_increment, *max_increment))
170-
});
171-
handles.push(handle);
172-
}
173-
}
174-
175-
let (min_expected, max_expected) = handles
176-
.into_iter()
177-
.map(|handle| handle.join().unwrap())
178-
.reduce(|(curr_min, curr_max), (min, max)| (curr_min + min, curr_max + max))
179-
.unwrap();
180-
181-
unsafe {
182-
assert!(get_int(instance_id) >= min_expected);
183-
assert!(get_int(instance_id) <= max_expected);
184-
}
185-
}

0 commit comments

Comments
 (0)