Skip to content

Commit 498543a

Browse files
committed
Fix reference counting in DispatchSemaphoreGuard::release
1 parent 09f6049 commit 498543a

File tree

1 file changed

+53
-10
lines changed

1 file changed

+53
-10
lines changed

crates/dispatch2/src/semaphore.rs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,74 @@ impl DispatchSemaphore {
2626
let result = Self::wait(self, timeout);
2727

2828
match result {
29-
0 => Ok(DispatchSemaphoreGuard(self.retain())),
29+
0 => Ok(DispatchSemaphoreGuard(ManuallyDrop::new(self.retain()))),
3030
_ => Err(WaitError::Timeout),
3131
}
3232
}
3333
}
3434

3535
/// Dispatch semaphore guard.
36+
///
37+
/// The semaphore will be signaled when the guard is dropped, or when [`release`] is called.
38+
///
39+
/// [`release`]: DispatchSemaphoreGuard::release
40+
//
41+
// See `release` for discussion of the `ManuallyDrop`.
3642
#[derive(Debug)]
37-
pub struct DispatchSemaphoreGuard(DispatchRetained<DispatchSemaphore>);
43+
pub struct DispatchSemaphoreGuard(ManuallyDrop<DispatchRetained<DispatchSemaphore>>);
3844

3945
impl DispatchSemaphoreGuard {
4046
/// Release the [`DispatchSemaphore`].
4147
pub fn release(self) -> bool {
42-
let this = ManuallyDrop::new(self);
43-
44-
// SAFETY: DispatchSemaphore cannot be null.
45-
let result = this.0.signal();
46-
47-
result != 0
48+
// We suppress `Drop` for the guard because that would signal the sempahore again.
49+
// The inner `DispatchRetained` is wrapped in `ManuallyDrop` so that it can be
50+
// separated from the guard and dropped normally.
51+
let mut this = ManuallyDrop::new(self);
52+
// SAFETY: The guard is being consumed; the `ManuallyDrop` contents will not be used again.
53+
let semaphore = unsafe { ManuallyDrop::take(&mut this.0) };
54+
semaphore.signal() != 0
4855
}
4956
}
5057

5158
impl Drop for DispatchSemaphoreGuard {
5259
fn drop(&mut self) {
53-
// SAFETY: DispatchSemaphore cannot be null.
54-
self.0.signal();
60+
// SAFETY: The guard is being dropped; the `ManuallyDrop` contents will not be used again.
61+
let semaphore = unsafe { ManuallyDrop::take(&mut self.0) };
62+
semaphore.signal();
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
#[allow(unused_imports)]
69+
use super::*;
70+
71+
#[test]
72+
#[cfg(feature = "objc2")]
73+
#[cfg_attr(
74+
not(target_vendor = "apple"),
75+
ignore = "only Apple's libdisptch is interoperable with `objc2`"
76+
)]
77+
fn acquire_release() {
78+
fn retain_count(semaphore: &DispatchSemaphore) -> usize {
79+
// SAFETY: semaphore is valid and the method signature is correct.
80+
unsafe { objc2::msg_send![semaphore, retainCount] }
81+
}
82+
83+
let semaphore = DispatchSemaphore::new(1);
84+
85+
assert_eq!(retain_count(&semaphore), 1);
86+
{
87+
let _guard = semaphore.try_acquire(DispatchTime::NOW).unwrap();
88+
assert_eq!(retain_count(&semaphore), 2);
89+
}
90+
assert_eq!(retain_count(&semaphore), 1);
91+
{
92+
let guard = semaphore.try_acquire(DispatchTime::NOW).unwrap();
93+
assert_eq!(retain_count(&semaphore), 2);
94+
guard.release();
95+
assert_eq!(retain_count(&semaphore), 1);
96+
}
97+
assert_eq!(retain_count(&semaphore), 1);
5598
}
5699
}

0 commit comments

Comments
 (0)