Skip to content

Commit e2c3610

Browse files
committed
Remove Mutex from GdCellInner.
--------- - Post-CR fixes (to squash later). - Properly lock cell state while dereferencing RefGuards. - Safety comments.
1 parent 42dd9f4 commit e2c3610

File tree

5 files changed

+95
-53
lines changed

5 files changed

+95
-53
lines changed

godot-cell/src/blocking_cell.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::cell::GdCellInner;
1717
/// Blocking version of [`panicking::GdCell`](crate::panicking::GdCell) for multithreaded usage.
1818
///
1919
/// 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`.
20+
/// Since `GdCellInner` isn't thread-safe by itself, any access to `inner` must be guarded by locking the `thread_tracker`.
2121
///
2222
/// For more details on when threads are being blocked see [`Self::borrow`] and [`Self::borrow_mut`].
2323
///
@@ -168,10 +168,11 @@ impl<T> GdCellBlocking<T> {
168168
}
169169
}
170170

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> {}
171+
// SAFETY:
172+
// - `T` must not be `Sync`, and the only way to access the underlying `T` is via `GdCellBlocking`.
173+
// - It must be ensured that `GdCellInner`, which holds `T`, cannot be accessed from multiple threads simultaneously while handing out guards.
174+
// The current implementation ensures this by locking the `thread_tracker`.
175+
unsafe impl<T: Send> Sync for GdCellBlocking<T> {}
175176

176177
/// Holds the reference count and the currently mutable thread.
177178
#[derive(Debug)]

godot-cell/src/blocking_guards.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,14 @@ impl<'a, T> Deref for MutGuardBlocking<'a, T> {
9090
type Target = <MutGuard<'a, T> as Deref>::Target;
9191

9292
fn deref(&self) -> &Self::Target {
93+
let _tracker_guard = self.state.lock().unwrap();
9394
self.inner.deref().deref()
9495
}
9596
}
9697

9798
impl<T> DerefMut for MutGuardBlocking<'_, T> {
9899
fn deref_mut(&mut self) -> &mut Self::Target {
100+
let _tracker_guard = self.state.lock().unwrap();
99101
self.inner.deref_mut().deref_mut()
100102
}
101103
}
@@ -135,26 +137,24 @@ impl<'a, T> InaccessibleGuardBlocking<'a, T> {
135137
/// logic may poison state, however it should not cause any UB either way.
136138
///
137139
/// 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+
pub fn try_drop(self) -> Result<(), Self> {
140141
let can_drop = {
141-
let _guard = manual.state.lock();
142-
manual.inner.can_drop()
142+
let _tracker_guard = self.state.lock();
143+
self.inner.can_drop()
143144
};
144145

145146
if !can_drop {
146-
Err(manual)
147+
Err(self)
147148
} else {
148-
unsafe { ManuallyDrop::drop(&mut manual) };
149149
Ok(())
150150
}
151151
}
152152
}
153153

154154
impl<T> Drop for InaccessibleGuardBlocking<'_, T> {
155155
fn drop(&mut self) {
156-
let state_lock = self.state.lock().unwrap();
156+
let _tracker_guard = self.state.lock().unwrap();
157157
unsafe { ManuallyDrop::drop(&mut self.inner) };
158-
drop(state_lock)
158+
drop(_tracker_guard)
159159
}
160160
}

godot-cell/src/cell.rs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,35 +89,45 @@ pub(crate) struct GdCellInner<T> {
8989
impl<T> GdCellInner<T> {
9090
/// Creates a new cell storing `value`.
9191
pub fn new(value: T) -> Pin<Box<Self>> {
92-
// Note – we are constructing it in two steps, because `CellState` uses non-null ptr to val inside our Cell.
9392
let mut uninitialized_cell: Box<MaybeUninit<Self>> = Box::new_uninit();
94-
let ptr = uninitialized_cell.as_mut_ptr();
93+
let uninitialized_cell_ptr = uninitialized_cell.as_mut_ptr();
9594

96-
unsafe {
97-
(&raw mut (*ptr).value).write(UnsafeCell::new(value));
98-
}
95+
// SAFETY: pointer to `value` is properly aligned.
96+
let value_ptr = unsafe {
97+
let value_ptr = &raw mut (*uninitialized_cell_ptr).value;
98+
value_ptr.write(UnsafeCell::new(value));
99+
value_ptr
100+
};
101+
102+
// SAFETY
103+
// `value_ptr` is properly aligned and points to initialized data.
104+
// Additionally, since Box::pin(...) is equivalent to Box::into_pin(Box::...) `value_ref`
105+
// will remain valid and refer to the same underlying value after pinning.
106+
let value_ref = unsafe { value_ptr.as_ref().unwrap() };
99107

100-
// SAFETY: Box::pin(...) is equivalent to Box::into_pin(Box::...) therefore our freshly initialized
101-
// `val` is and will stay valid (i.e. will refer to the same place after pinning).
108+
// SAFETY: pointer to `state` is properly aligned.
109+
let state_ptr = unsafe { &raw mut (*uninitialized_cell_ptr).state };
110+
111+
// SAFETY: See above.
102112
unsafe {
103-
let val = (&raw const (*ptr).value).as_ref().unwrap();
104-
(&raw mut (*ptr).state).write(UnsafeCell::new(CellState::new(val)));
113+
state_ptr.write(UnsafeCell::new(CellState::new(value_ref)));
105114
}
106115

107-
Box::into_pin(unsafe { uninitialized_cell.assume_init() })
116+
Box::into_pin(
117+
// SAFETY: All `GdCellInner` fields are valid.
118+
unsafe { uninitialized_cell.assume_init() },
119+
)
108120
}
109121

110122
/// Returns a new shared reference to the contents of the cell.
111123
///
112124
/// Fails if an accessible mutable reference exists.
113125
pub fn borrow(self: Pin<&Self>) -> Result<RefGuard<'_, T>, Box<dyn Error>> {
114-
{
115-
let state = unsafe { &mut *self.state.get() };
116-
state.borrow_state.increment_shared()?;
117-
}
118-
119-
let state = unsafe { &*self.state.get() };
126+
// SAFETY: This is the only active reference to the state.
127+
let state = unsafe { self.cell_state_mut() };
128+
state.borrow_state.increment_shared()?;
120129
let value = state.get_ptr();
130+
121131
// SAFETY: `increment_shared` succeeded, therefore there cannot currently be any accessible mutable
122132
// references.
123133
unsafe { Ok(RefGuard::new(&self.get_ref().state, value)) }
@@ -127,7 +137,8 @@ impl<T> GdCellInner<T> {
127137
///
128138
/// Fails if an accessible mutable reference exists, or a shared reference exists.
129139
pub fn borrow_mut(self: Pin<&Self>) -> Result<MutGuard<'_, T>, Box<dyn Error>> {
130-
let state = unsafe { &mut *self.state.get() };
140+
// SAFETY: This is the only active reference to the state.
141+
let state = unsafe { self.cell_state_mut() };
131142
state.borrow_state.increment_mut()?;
132143
let count = state.borrow_state.mut_count();
133144
let value = state.get_ptr();
@@ -159,6 +170,25 @@ impl<T> GdCellInner<T> {
159170
InaccessibleGuard::new(&self.get_ref().state, current_ref)
160171
}
161172

173+
/// Returns a reference to the CellState.
174+
///
175+
/// # Safety
176+
/// - The caller must ensure that there are no active exclusive references to the given state.
177+
unsafe fn cell_state(&self) -> &CellState<T> {
178+
// SAFETY: the underlying `CellState` will not be deallocated as long as Cell itself is alive.
179+
unsafe { &*self.state.get() }
180+
}
181+
182+
/// Returns the exclusive reference to the CellState.
183+
///
184+
/// # Safety
185+
/// - The caller must ensure that there are no active references to the given state.
186+
#[allow(clippy::mut_from_ref)]
187+
unsafe fn cell_state_mut(&self) -> &mut CellState<T> {
188+
// SAFETY: the underlying `CellState` will not be deallocated as long as Cell itself is alive.
189+
unsafe { &mut *self.state.get() }
190+
}
191+
162192
/// Returns `true` if there are any mutable or shared references, regardless of whether the mutable
163193
/// references are accessible or not.
164194
///
@@ -169,16 +199,15 @@ impl<T> GdCellInner<T> {
169199
/// cell hands out a new borrow before it is destroyed. So we still need to ensure that this cannot
170200
/// happen at the same time.
171201
pub fn is_currently_bound(self: Pin<&Self>) -> bool {
172-
let state = unsafe { &*self.state.get() };
173-
202+
// SAFETY: this is the only reference to the `cell_state` in given context.
203+
let state = unsafe { self.cell_state() };
174204
state.borrow_state.shared_count() > 0 || state.borrow_state.mut_count() > 0
175205
}
176206

177207
/// Similar to [`Self::is_currently_bound`] but only counts mutable references and ignores shared references.
178208
pub(crate) fn is_currently_mutably_bound(self: Pin<&Self>) -> bool {
179-
let state = unsafe { &*self.state.get() };
180-
181-
state.borrow_state.mut_count() > 0
209+
// SAFETY: this is the only reference to the `cell_state` in given context.
210+
unsafe { self.cell_state() }.borrow_state.mut_count() > 0
182211
}
183212
}
184213

@@ -206,8 +235,6 @@ pub(crate) struct CellState<T> {
206235
}
207236

208237
impl<T> CellState<T> {
209-
/// Create a new uninitialized state. Use [`initialize_ptr()`](CellState::initialize_ptr()) to initialize
210-
/// it.
211238
fn new(value: &UnsafeCell<T>) -> Self {
212239
Self {
213240
borrow_state: BorrowState::new(),
@@ -234,6 +261,17 @@ impl<T> CellState<T> {
234261
self.stack_depth -= 1;
235262
self.stack_depth
236263
}
264+
265+
/// Returns underlying [`BorrowState`].
266+
///
267+
/// # Safety
268+
///
269+
/// - `cell_state` must point to a valid reference.
270+
/// - There can't be any active reference to `CellState`.
271+
#[allow(clippy::mut_from_ref)]
272+
pub(crate) unsafe fn borrow_state(cell_state: &UnsafeCell<Self>) -> &mut BorrowState {
273+
&mut cell_state.get().as_mut().unwrap().borrow_state
274+
}
237275
}
238276

239277
#[cfg(test)]

godot-cell/src/guards.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ impl<T> Deref for RefGuard<'_, T> {
5555

5656
impl<T> Drop for RefGuard<'_, T> {
5757
fn drop(&mut self) {
58-
unsafe { self.state.get().as_mut() }
59-
.unwrap()
60-
.borrow_state
58+
// SAFETY: There can't be any other active reference to CellState.
59+
unsafe { CellState::borrow_state(self.state) }
6160
.decrement_shared()
6261
.unwrap();
6362
}
@@ -124,9 +123,8 @@ impl<T> Deref for MutGuard<'_, T> {
124123
type Target = T;
125124

126125
fn deref(&self) -> &Self::Target {
127-
let count = unsafe { self.state.get().as_ref().unwrap() }
128-
.borrow_state
129-
.mut_count();
126+
// SAFETY: There can't be any other active reference to CellState.
127+
let count = unsafe { CellState::borrow_state(self.state) }.mut_count();
130128
// This is just a best-effort error check. It should never be triggered.
131129
assert_eq!(
132130
self.count,
@@ -152,9 +150,8 @@ impl<T> Deref for MutGuard<'_, T> {
152150

153151
impl<T> DerefMut for MutGuard<'_, T> {
154152
fn deref_mut(&mut self) -> &mut Self::Target {
155-
let count = unsafe { self.state.get().as_ref().unwrap() }
156-
.borrow_state
157-
.mut_count();
153+
// SAFETY: There can't be any other active reference to CellState.
154+
let count = unsafe { CellState::borrow_state(self.state) }.mut_count();
158155
// This is just a best-effort error check. It should never be triggered.
159156
assert_eq!(
160157
self.count,
@@ -181,8 +178,7 @@ impl<T> DerefMut for MutGuard<'_, T> {
181178

182179
impl<T> Drop for MutGuard<'_, T> {
183180
fn drop(&mut self) {
184-
unsafe { self.state.get().as_mut().unwrap() }
185-
.borrow_state
181+
unsafe { CellState::borrow_state(self.state) }
186182
.decrement_mut()
187183
.unwrap();
188184
}
@@ -222,6 +218,7 @@ impl<'a, T> InaccessibleGuard<'a, T> {
222218
where
223219
'a: 'b,
224220
{
221+
// SAFETY: There can be only one active reference to the cell state at a given time.
225222
let cell_state = unsafe { state.get().as_mut() }.unwrap();
226223
let current_ptr = cell_state.get_ptr();
227224
let new_ptr = NonNull::from(new_ref);
@@ -255,6 +252,11 @@ impl<'a, T> InaccessibleGuard<'a, T> {
255252
state.pop_ptr(prev_ptr);
256253
}
257254

255+
/// Returns `true` if guard can be safely dropped, i.e.:
256+
///
257+
/// - Guard is being released in correct order.
258+
/// - There is no accessible mutable reference to underlying value.
259+
/// - There are no shared references to underlying value.
258260
#[doc(hidden)]
259261
pub fn can_drop(&self) -> bool {
260262
let state = unsafe { self.state.get().as_mut() }.unwrap();
@@ -266,11 +268,12 @@ impl<'a, T> InaccessibleGuard<'a, T> {
266268
/// Used currently in the mock-tests, as we need a thread safe way to drop self. Using the normal drop
267269
/// logic may poison state, however it should not cause any UB either way.
268270
#[doc(hidden)]
269-
pub fn try_drop(self) -> Result<(), std::mem::ManuallyDrop<Self>> {
270-
let manual = std::mem::ManuallyDrop::new(self);
271-
if !manual.can_drop() {
272-
return Err(manual);
271+
pub fn try_drop(self) -> Result<(), Self> {
272+
if !self.can_drop() {
273+
return Err(self);
273274
}
275+
276+
let manual = std::mem::ManuallyDrop::new(self);
274277
Self::perform_drop(manual.state, manual.prev_ptr, manual.stack_depth);
275278

276279
Ok(())

godot-cell/tests/mock/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ macro_rules! setup_mock {
135135

136136
while let Some(guard) = guard_opt.take() {
137137
if let Err(new_guard) = std::mem::ManuallyDrop::into_inner(guard).try_drop() {
138-
guard_opt = Some(new_guard);
138+
guard_opt = Some(std::mem::ManuallyDrop::new(new_guard));
139139
std::hint::spin_loop()
140140
}
141141
}

0 commit comments

Comments
 (0)