Skip to content

Commit b5e5403

Browse files
madsmtmMarijnS95
andcommitted
Add various DispatchData helper functions
Replaces #710. Co-authored-by: Marijn Suijten <[email protected]>
1 parent 927af48 commit b5e5403

File tree

5 files changed

+243
-4
lines changed

5 files changed

+243
-4
lines changed

crates/dispatch2/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1414
retain/release operations on dispatch objects, similar in spirit to
1515
`objc2::rc::Retained`. This mostly replaces `DispatchObject<T>`.
1616
- Implement `Send` and `Sync` for dispatch objects.
17+
- Added `DispatchData` helper functions.
1718

1819
### Changed
1920
- Changed how memory management works to match other `objc2` crates. Instead

crates/dispatch2/src/data.rs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,254 @@
1+
use core::ptr::{self, NonNull};
2+
3+
use crate::generated::_dispatch_data_empty;
4+
use crate::DispatchRetained;
5+
16
dispatch_object!(
27
/// Dispatch data.
38
#[doc(alias = "dispatch_data_t")]
49
#[doc(alias = "dispatch_data_s")]
510
pub struct DispatchData;
611
);
712

13+
impl DispatchData {
14+
// TODO: Expose this once possible in MSRV.
15+
// pub const EMPTY: &Self = _dispatch_data_empty;
16+
17+
/// Get an empty [`DispatchData`].
18+
pub fn empty() -> &'static Self {
19+
// SAFETY: The static is valid.
20+
unsafe { &_dispatch_data_empty }
21+
}
22+
23+
/// Creates a dispatch data object with a copy of the given contiguous
24+
/// buffer of memory.
25+
#[cfg(feature = "block2")]
26+
pub fn from_bytes(data: &[u8]) -> DispatchRetained<Self> {
27+
// TODO: Autogenerate?
28+
const DISPATCH_DATA_DESTRUCTOR_DEFAULT: crate::dispatch_block_t = ptr::null_mut();
29+
30+
let ptr = NonNull::new(data.as_ptr().cast_mut()).unwrap().cast();
31+
32+
// We don't care which queue ends up running the destructor.
33+
let queue = None;
34+
35+
// SAFETY: Buffer pointer is valid for the given number of bytes.
36+
//
37+
// The destructor is DISPATCH_DATA_DESTRUCTOR_DEFAULT, which indicates
38+
// the buffer should be copied, so it's safe to keep after the end of
39+
// this function.
40+
unsafe { Self::new(ptr, data.len(), queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT) }
41+
}
42+
43+
/// Creates a dispatch data object with a reference to the given
44+
/// contiguous buffer of memory.
45+
#[cfg(feature = "block2")]
46+
pub fn from_static_bytes(data: &'static [u8]) -> DispatchRetained<Self> {
47+
block2::global_block! {
48+
static NOOP_BLOCK = || {}
49+
}
50+
51+
let ptr = NonNull::new(data.as_ptr().cast_mut()).unwrap().cast();
52+
53+
// We don't care which queue ends up running the destructor.
54+
let queue = None;
55+
56+
let destructor = (&*NOOP_BLOCK as *const block2::Block<_>).cast_mut();
57+
58+
// SAFETY: Buffer pointer is valid for the given number of bytes.
59+
// Queue handle is valid, and the destructor is a NULL value which
60+
// indicates the buffer should be copied.
61+
unsafe { Self::new(ptr, data.len(), queue, destructor) }
62+
}
63+
64+
/// Creates a dispatch data object with ownership of the given contiguous
65+
/// buffer of memory.
66+
#[cfg(feature = "alloc")]
67+
#[cfg(feature = "block2")]
68+
pub fn from_boxed(data: alloc::boxed::Box<[u8]>) -> DispatchRetained<Self> {
69+
let data_len = data.len();
70+
let raw = alloc::boxed::Box::into_raw(data);
71+
let ptr = NonNull::new(raw).unwrap().cast();
72+
73+
let destructor = block2::RcBlock::new(move || {
74+
// SAFETY: The fat pointer (plus size) was retrieved from
75+
// `Box::into_raw()`, and its ownership was *not* consumed by
76+
// dispatch_data_create().
77+
let _ = unsafe { alloc::boxed::Box::<[u8]>::from_raw(raw) };
78+
});
79+
let destructor = block2::RcBlock::as_ptr(&destructor);
80+
81+
// We don't care which queue ends up running the destructor.
82+
// Box<[u8]> is sendable, so it's fine for us to potentially pass it
83+
// to a different thread.
84+
let queue = None;
85+
86+
// SAFETY: Buffer pointer is valid for the given number of bytes.
87+
//
88+
// The destructor is valid and correctly destroys the buffer.
89+
unsafe { Self::new(ptr, data_len, queue, destructor) }
90+
}
91+
92+
/// Copy all the non-contiguous parts of the data into a contiguous
93+
/// [`Vec`][std::vec::Vec].
94+
///
95+
/// # Examples
96+
///
97+
/// ```
98+
/// use dispatch2::DispatchData;
99+
///
100+
/// let data = DispatchData::from_bytes(b"foo");
101+
/// assert_eq!(data.to_vec(), b"foo");
102+
/// ```
103+
#[cfg(feature = "alloc")]
104+
#[cfg(feature = "block2")]
105+
#[cfg(feature = "objc2")]
106+
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
107+
let contents = core::cell::RefCell::new(alloc::vec::Vec::new());
108+
let block = block2::RcBlock::new(
109+
|_region, _offset, buffer: NonNull<core::ffi::c_void>, size| {
110+
// SAFETY: Dispatch guarantees that the slice is valid.
111+
let buffer =
112+
unsafe { core::slice::from_raw_parts(buffer.cast::<u8>().as_ptr(), size) };
113+
contents.borrow_mut().extend_from_slice(buffer);
114+
1
115+
},
116+
);
117+
118+
let block = block2::RcBlock::as_ptr(&block);
119+
// SAFETY: Transmute from return type `u8` to `bool` is safe, since we
120+
// only ever return `1` / `true`.
121+
// TODO: Fix the need for this in `block2`.
122+
let block = unsafe {
123+
core::mem::transmute::<
124+
*mut block2::Block<
125+
dyn Fn(NonNull<DispatchData>, usize, NonNull<core::ffi::c_void>, usize) -> u8,
126+
>,
127+
*mut block2::Block<
128+
dyn Fn(NonNull<DispatchData>, usize, NonNull<core::ffi::c_void>, usize) -> bool,
129+
>,
130+
>(block)
131+
};
132+
133+
// SAFETY: The block is implemented correctly.
134+
unsafe { self.apply(block) };
135+
contents.take()
136+
}
137+
}
138+
8139
#[cfg(test)]
140+
#[cfg(feature = "alloc")]
141+
#[cfg(feature = "block2")]
9142
mod tests {
143+
use core::time::Duration;
144+
use std::{
145+
boxed::Box,
146+
sync::{Arc, Condvar, Mutex},
147+
};
148+
149+
use block2::RcBlock;
150+
151+
use crate::DispatchObject;
152+
10153
use super::*;
11154

12155
// Intentionally not Send + Sync, as `DispatchData` can contain things
13156
// like `NSData`.
14157
static_assertions::assert_not_impl_any!(DispatchData: Send, Sync);
158+
159+
#[test]
160+
fn create() {
161+
let data = DispatchData::from_bytes(b"foo");
162+
assert_eq!(data.size(), 3);
163+
164+
let data = DispatchData::from_static_bytes(b"foo");
165+
assert_eq!(data.size(), 3);
166+
167+
let data = DispatchData::from_boxed(Box::from(b"foo" as &[u8]));
168+
assert_eq!(data.size(), 3);
169+
}
170+
171+
#[test]
172+
fn concat() {
173+
let data1 = DispatchData::from_bytes(b"foo");
174+
let data2 = DispatchData::from_boxed(Box::from(b"bar" as &[u8]));
175+
let extended = data1.concat(&data2).concat(&data2);
176+
assert_eq!(extended.to_vec(), "foobarbar".as_bytes());
177+
}
178+
179+
// Test destruction, and that it still works when we add a finalizer to the data.
180+
#[test]
181+
fn with_finalizer() {
182+
#[derive(Debug)]
183+
struct State {
184+
has_run_destructor: bool,
185+
has_run_finalizer: bool,
186+
}
187+
188+
let state = State {
189+
has_run_destructor: false,
190+
has_run_finalizer: false,
191+
};
192+
let pair = Arc::new((Mutex::new(state), Condvar::new()));
193+
194+
let pair2 = Arc::clone(&pair);
195+
let destructor = RcBlock::new(move || {
196+
let (lock, cvar) = &*pair2;
197+
lock.lock().unwrap().has_run_destructor = true;
198+
cvar.notify_one();
199+
});
200+
201+
// SAFETY: The pointers are correct.
202+
let data = unsafe {
203+
let data = b"xyz";
204+
DispatchData::new(
205+
NonNull::new(data.as_ptr().cast_mut()).unwrap().cast(),
206+
data.len(),
207+
None,
208+
RcBlock::as_ptr(&destructor),
209+
)
210+
};
211+
212+
let pair3 = Arc::clone(&pair);
213+
data.set_finalizer(move || {
214+
let (lock, cvar) = &*pair3;
215+
lock.lock().unwrap().has_run_finalizer = true;
216+
cvar.notify_one();
217+
});
218+
219+
let (lock, cvar) = &*pair;
220+
let lock = lock.lock().unwrap();
221+
222+
// Verify the destructor hasn't run yet.
223+
let (lock, res) = cvar.wait_timeout(lock, Duration::from_millis(10)).unwrap();
224+
assert!(res.timed_out());
225+
assert!(!lock.has_run_destructor);
226+
assert!(!lock.has_run_finalizer);
227+
228+
let data2 = data.clone();
229+
drop(data);
230+
231+
// Still not yet, the second reference is still alive.
232+
let (lock, res) = cvar.wait_timeout(lock, Duration::from_millis(10)).unwrap();
233+
assert!(res.timed_out());
234+
assert!(!lock.has_run_destructor);
235+
assert!(!lock.has_run_finalizer);
236+
237+
let data3 = data2.concat(&DispatchData::from_bytes(b"foo"));
238+
drop(data2);
239+
240+
// Still not yet, the reference is kept alive by the new data.
241+
let (lock, res) = cvar.wait_timeout(lock, Duration::from_millis(10)).unwrap();
242+
assert!(res.timed_out());
243+
assert!(!lock.has_run_destructor);
244+
assert!(!lock.has_run_finalizer);
245+
246+
drop(data3);
247+
248+
// Has run now!
249+
let (lock, res) = cvar.wait_timeout(lock, Duration::from_millis(10)).unwrap();
250+
assert!(!res.timed_out());
251+
assert!(lock.has_run_destructor);
252+
assert!(lock.has_run_finalizer);
253+
}
15254
}

crates/dispatch2/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ pub use self::data::DispatchData;
7070
pub use self::generated::{
7171
dispatch_allow_send_signals, dispatch_fd_t, dispatch_get_specific, dispatch_once_t,
7272
DispatchAutoReleaseFrequency, _dispatch_data_destructor_free, _dispatch_data_destructor_munmap,
73-
_dispatch_data_empty, _dispatch_main_q, _dispatch_queue_attr_concurrent,
7473
_dispatch_source_type_data_add, _dispatch_source_type_data_or,
7574
_dispatch_source_type_data_replace, _dispatch_source_type_mach_recv,
7675
_dispatch_source_type_mach_send, _dispatch_source_type_memorypressure,

crates/dispatch2/src/object.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub unsafe trait DispatchObject {
107107
}
108108

109109
/// Set the finalizer function for the object.
110-
fn set_finalizer<F>(&mut self, destructor: F)
110+
fn set_finalizer<F>(&self, destructor: F)
111111
where
112112
F: Send + FnOnce(),
113113
{
@@ -158,7 +158,7 @@ pub unsafe trait DispatchObject {
158158
}
159159

160160
/// Activate the object.
161-
fn activate(&mut self) {
161+
fn activate(&self) {
162162
dispatch_activate(self.as_raw());
163163
}
164164

crates/dispatch2/src/queue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl DispatchQueue {
188188
}
189189

190190
/// Sets a function at the given key that will be executed at [`DispatchQueue`] destruction.
191-
pub fn set_specific<F>(&mut self, key: NonNull<()>, destructor: F)
191+
pub fn set_specific<F>(&self, key: NonNull<()>, destructor: F)
192192
where
193193
F: Send + FnOnce(),
194194
{

0 commit comments

Comments
 (0)