use std::{
ffi::CString,
io,
os::raw::{c_char, c_long, c_void},
ptr,
};
#[link(name = "System", kind = "framework")]
unsafe extern "C" {
fn dispatch_queue_create(label: *const c_char, attr: *mut c_void) -> *mut c_void;
fn dispatch_release(object: *mut c_void);
fn dispatch_semaphore_create(value: c_long) -> *mut c_void;
fn dispatch_semaphore_signal(dsema: *mut c_void) -> c_long;
fn dispatch_semaphore_wait(dsema: *mut c_void, timeout: u64) -> c_long;
fn dispatch_time(when: u64, delta: i64) -> u64;
}
pub const DISPATCH_TIME_FOREVER: u64 = u64::MAX;
const DISPATCH_TIME_NOW: u64 = 0;
#[must_use]
pub fn dispatch_time_now_plus_ns(delta_ns: u64) -> u64 {
let delta = i64::try_from(delta_ns).unwrap_or(i64::MAX);
unsafe { dispatch_time(DISPATCH_TIME_NOW, delta) }
}
#[derive(Debug)]
pub struct Queue {
raw: *mut c_void,
}
unsafe impl Send for Queue {}
unsafe impl Sync for Queue {}
impl Queue {
pub fn create_serial(label: &str) -> Result<Self, io::Error> {
let cstr = CString::new(label).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("dispatch queue label contains NUL: {e}"),
)
})?;
let raw = unsafe { dispatch_queue_create(cstr.as_ptr(), ptr::null_mut()) };
if raw.is_null() {
return Err(io::Error::new(
io::ErrorKind::OutOfMemory,
"dispatch_queue_create returned NULL",
));
}
Ok(Self { raw })
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.raw
}
}
impl Drop for Queue {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { dispatch_release(self.raw) };
}
}
}
#[derive(Debug)]
pub struct Semaphore {
raw: *mut c_void,
}
unsafe impl Send for Semaphore {}
unsafe impl Sync for Semaphore {}
impl Semaphore {
#[must_use]
pub fn new() -> Self {
let raw = unsafe { dispatch_semaphore_create(0) };
Self { raw }
}
pub fn wait(&self, timeout: u64) -> bool {
unsafe { dispatch_semaphore_wait(self.raw, timeout) == 0 }
}
pub fn signal(&self) -> bool {
unsafe { dispatch_semaphore_signal(self.raw) != 0 }
}
}
impl Default for Semaphore {
fn default() -> Self {
Self::new()
}
}
impl Drop for Semaphore {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { dispatch_release(self.raw) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_signal_and_wait_a_dispatch_semaphore() {
let sem = Semaphore::new();
let _ = sem.signal();
assert!(sem.wait(DISPATCH_TIME_FOREVER));
}
#[test]
fn test_should_create_and_drop_a_serial_queue() {
let q = Queue::create_serial("squib-net.tests").expect("queue creation");
assert!(!q.as_ptr().is_null());
}
#[test]
fn test_should_reject_label_with_nul_byte() {
let err = Queue::create_serial("bad\0label").unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
}