Skip to content

Commit dcfd5df

Browse files
authored
Merge pull request #736 from TitanNano/jovan/blocking_godot_cell
Block `GdCell::borrow` and `GdCell::borrow_mut` on other threads
2 parents 6c92a05 + efa52b7 commit dcfd5df

14 files changed

+1368
-789
lines changed

godot-cell/src/blocking_cell.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use std::collections::HashMap;
9+
use std::error::Error;
10+
use std::pin::Pin;
11+
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
12+
use std::thread;
13+
14+
use crate::blocking_guards::{MutGuardBlocking, RefGuardBlocking};
15+
use crate::cell::GdCellInner;
16+
use crate::guards::InaccessibleGuard;
17+
18+
/// Blocking version of [`panicking::GdCell`](crate::panicking::GdCell) for multi-threaded usage.
19+
///
20+
/// This version of GdCell blocks the current thread if it does not yet hold references to the cell.
21+
///
22+
/// For more details on when threads are being blocked see [`Self::borrow`] and [`Self::borrow_mut`].
23+
///
24+
/// See [`panicking::GdCell`](crate::panicking::GdCell) for more details on the base concept of this type.
25+
pub struct GdCellBlocking<T> {
26+
inner: Pin<Box<GdCellInner<T>>>,
27+
thread_tracker: Arc<Mutex<ThreadTracker>>,
28+
immut_condition: Arc<Condvar>,
29+
mut_condition: Arc<Condvar>,
30+
}
31+
32+
impl<T> GdCellBlocking<T> {
33+
pub fn new(value: T) -> Self {
34+
Self {
35+
inner: GdCellInner::new(value),
36+
thread_tracker: Arc::default(),
37+
immut_condition: Arc::new(Condvar::new()),
38+
mut_condition: Arc::new(Condvar::new()),
39+
}
40+
}
41+
42+
/// Returns a new shared reference to the contents of the cell.
43+
///
44+
/// Fails if an accessible mutable reference exists on the current thread.
45+
///
46+
/// Blocks if another thread currently holds a mutable reference.
47+
pub fn borrow(&self) -> Result<RefGuardBlocking<'_, T>, Box<dyn Error>> {
48+
let mut tracker_guard = self.thread_tracker.lock().unwrap();
49+
50+
if self.inner.as_ref().is_currently_mutably_bound()
51+
&& !tracker_guard.current_thread_has_mut_ref()
52+
{
53+
// Block current thread until borrow becomes available.
54+
tracker_guard = self.block_immut(tracker_guard);
55+
}
56+
57+
let inner_guard = self.inner.as_ref().borrow()?;
58+
59+
tracker_guard.increment_current_thread_shared_count();
60+
61+
Ok(RefGuardBlocking::new(
62+
inner_guard,
63+
self.mut_condition.clone(),
64+
self.thread_tracker.clone(),
65+
))
66+
}
67+
68+
/// Returns a new mutable reference to the contents of the cell.
69+
///
70+
/// Fails if an accessible mutable reference, or a shared reference exists on the current thread.
71+
///
72+
/// Blocks if another thread currently holds a mutable reference, or if another thread holds immutable references but the current thread
73+
/// doesn't.
74+
pub fn borrow_mut(&self) -> Result<MutGuardBlocking<'_, T>, Box<dyn Error>> {
75+
let mut tracker_guard = self.thread_tracker.lock().unwrap();
76+
77+
if self.inner.as_ref().is_currently_bound()
78+
&& tracker_guard.current_thread_shared_count() == 0
79+
&& !tracker_guard.current_thread_has_mut_ref()
80+
{
81+
// Block current thread until borrow becomes available.
82+
tracker_guard = self.block_mut(tracker_guard);
83+
}
84+
85+
let inner_guard = self.inner.as_ref().borrow_mut()?;
86+
87+
tracker_guard.mut_thread = thread::current().id();
88+
89+
Ok(MutGuardBlocking::new(
90+
inner_guard,
91+
self.mut_condition.clone(),
92+
self.immut_condition.clone(),
93+
))
94+
}
95+
96+
/// Make the current mutable borrow inaccessible, thus freeing the value up to be reborrowed again.
97+
///
98+
/// Will error if:
99+
/// - There is currently no accessible mutable borrow.
100+
/// - There are any shared references.
101+
/// - `current_ref` is not equal to the pointer in `self.inner.state`.
102+
pub fn make_inaccessible<'cell, 'val>(
103+
&'cell self,
104+
current_ref: &'val mut T,
105+
) -> Result<InaccessibleGuard<'val, T>, Box<dyn Error>>
106+
where
107+
'cell: 'val,
108+
{
109+
self.inner.as_ref().make_inaccessible(current_ref)
110+
}
111+
112+
/// Returns `true` if there are any mutable or shared references, regardless of whether the mutable
113+
/// references are accessible or not.
114+
///
115+
/// In particular this means that it is safe to destroy this cell and the value contained within, as no
116+
/// references can exist that can reference this cell.
117+
///
118+
/// Keep in mind that in multithreaded code it is still possible for this to return true, and then the
119+
/// cell hands out a new borrow before it is destroyed. So we still need to ensure that this cannot
120+
/// happen at the same time.
121+
pub fn is_currently_bound(&self) -> bool {
122+
self.inner.as_ref().is_currently_bound()
123+
}
124+
125+
/// Blocks the current thread until all mutable and shared references have been dropped.
126+
fn block_mut<'a>(
127+
&self,
128+
mut tracker_guard: MutexGuard<'a, ThreadTracker>,
129+
) -> MutexGuard<'a, ThreadTracker> {
130+
while self.inner.as_ref().is_currently_bound() {
131+
tracker_guard = self.mut_condition.wait(tracker_guard).unwrap();
132+
}
133+
134+
tracker_guard
135+
}
136+
137+
/// Blocks the current thread until all mutable references have been dropped.
138+
fn block_immut<'a>(
139+
&self,
140+
mut tracker_guard: MutexGuard<'a, ThreadTracker>,
141+
) -> MutexGuard<'a, ThreadTracker> {
142+
while self.inner.as_ref().is_currently_mutably_bound() {
143+
tracker_guard = self.immut_condition.wait(tracker_guard).unwrap();
144+
}
145+
146+
tracker_guard
147+
}
148+
}
149+
150+
/// Holds the reference count and the currently mutable thread.
151+
#[derive(Debug)]
152+
pub(crate) struct ThreadTracker {
153+
/// Thread ID of the thread that currently can hold the mutable reference.
154+
mut_thread: thread::ThreadId,
155+
156+
/// Shared reference count per thread.
157+
shared_counts: HashMap<thread::ThreadId, usize>,
158+
}
159+
160+
impl Default for ThreadTracker {
161+
fn default() -> Self {
162+
Self {
163+
mut_thread: thread::current().id(),
164+
shared_counts: HashMap::new(),
165+
}
166+
}
167+
}
168+
169+
impl ThreadTracker {
170+
/// Number of shared references in the current thread.
171+
pub fn current_thread_shared_count(&self) -> usize {
172+
*self
173+
.shared_counts
174+
.get(&thread::current().id())
175+
.unwrap_or(&0)
176+
}
177+
178+
/// Increments the shared reference count in the current thread.
179+
pub fn increment_current_thread_shared_count(&mut self) {
180+
self.shared_counts
181+
.entry(thread::current().id())
182+
.and_modify(|count| *count += 1)
183+
.or_insert(1);
184+
}
185+
186+
/// Decrements the shared reference count in the current thread.
187+
pub fn decrement_current_thread_shared_count(&mut self) {
188+
let thread_id = thread::current().id();
189+
let entry = self.shared_counts.get_mut(&thread_id);
190+
191+
debug_assert!(
192+
entry.is_some(),
193+
"No shared reference count exists for {thread_id:?}."
194+
);
195+
196+
let Some(count) = entry else {
197+
return;
198+
};
199+
200+
*count -= 1;
201+
}
202+
203+
/// Returns if the current thread can hold the mutable reference.
204+
pub fn current_thread_has_mut_ref(&self) -> bool {
205+
self.mut_thread == thread::current().id()
206+
}
207+
}

godot-cell/src/blocking_guards.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use std::ops::{Deref, DerefMut};
9+
use std::sync::{Arc, Condvar, Mutex};
10+
11+
use crate::blocking_cell::ThreadTracker;
12+
use crate::guards::{MutGuard, RefGuard};
13+
14+
/// Extended version of [`panicking::RefGuard`](crate::panicking::RefGuard) that tracks which thread a reference belongs to and when it's dropped.
15+
///
16+
/// See [`panicking::RefGuard`](crate::panicking::RefGuard) for more details.
17+
#[derive(Debug)]
18+
pub struct RefGuardBlocking<'a, T> {
19+
inner: Option<RefGuard<'a, T>>,
20+
mut_condition: Arc<Condvar>,
21+
state: Arc<Mutex<ThreadTracker>>,
22+
}
23+
24+
impl<'a, T> RefGuardBlocking<'a, T> {
25+
pub(crate) fn new(
26+
inner: RefGuard<'a, T>,
27+
mut_condition: Arc<Condvar>,
28+
state: Arc<Mutex<ThreadTracker>>,
29+
) -> Self {
30+
Self {
31+
inner: Some(inner),
32+
mut_condition,
33+
state,
34+
}
35+
}
36+
}
37+
38+
impl<'a, T> Deref for RefGuardBlocking<'a, T> {
39+
type Target = <RefGuard<'a, T> as Deref>::Target;
40+
41+
fn deref(&self) -> &Self::Target {
42+
self.inner.as_ref().unwrap().deref()
43+
}
44+
}
45+
46+
impl<'a, T> Drop for RefGuardBlocking<'a, T> {
47+
fn drop(&mut self) {
48+
let mut state_lock = self.state.lock().unwrap();
49+
50+
state_lock.decrement_current_thread_shared_count();
51+
52+
drop(self.inner.take());
53+
54+
self.mut_condition.notify_one();
55+
drop(state_lock);
56+
}
57+
}
58+
59+
/// Extended version of [`panicking::MutGuard`](crate::panicking::MutGuard) that tracks which thread a reference belongs to and when it's dropped.
60+
///
61+
/// See [`panicking::MutGuard`](crate::panicking::MutGuard) for more details.
62+
#[derive(Debug)]
63+
pub struct MutGuardBlocking<'a, T> {
64+
inner: Option<MutGuard<'a, T>>,
65+
mut_condition: Arc<Condvar>,
66+
immut_condition: Arc<Condvar>,
67+
}
68+
69+
impl<'a, T> MutGuardBlocking<'a, T> {
70+
pub(crate) fn new(
71+
inner: MutGuard<'a, T>,
72+
mut_condition: Arc<Condvar>,
73+
immut_condition: Arc<Condvar>,
74+
) -> Self {
75+
Self {
76+
inner: Some(inner),
77+
immut_condition,
78+
mut_condition,
79+
}
80+
}
81+
}
82+
83+
impl<'a, T> Deref for MutGuardBlocking<'a, T> {
84+
type Target = <MutGuard<'a, T> as Deref>::Target;
85+
86+
fn deref(&self) -> &Self::Target {
87+
self.inner.as_ref().unwrap().deref()
88+
}
89+
}
90+
91+
impl<'a, T> DerefMut for MutGuardBlocking<'a, T> {
92+
fn deref_mut(&mut self) -> &mut Self::Target {
93+
self.inner.as_mut().unwrap().deref_mut()
94+
}
95+
}
96+
97+
impl<'a, T> Drop for MutGuardBlocking<'a, T> {
98+
fn drop(&mut self) {
99+
drop(self.inner.take());
100+
101+
self.mut_condition.notify_one();
102+
self.immut_condition.notify_all();
103+
}
104+
}

0 commit comments

Comments
 (0)