use std::ffi::CStr;
use std::sync::Arc;
use super::sys;
use super::{Error, Result};
#[derive(Clone, Copy)]
pub(super) struct Vtable {
pub(super) new: sys::CancelNewFn,
pub(super) request: sys::CancelRequestFn,
pub(super) free: sys::CancelFreeFn,
}
impl Vtable {
pub(super) fn resolve(
resolve: impl Fn(&CStr) -> Option<*mut std::ffi::c_void>,
) -> Option<Self> {
let (new, request, free) = (
resolve(c"irgx_cancel_new")?,
resolve(c"irgx_cancel_request")?,
resolve(c"irgx_cancel_free")?,
);
Some(unsafe {
Self {
new: std::mem::transmute::<*mut std::ffi::c_void, sys::CancelNewFn>(new),
request: std::mem::transmute::<*mut std::ffi::c_void, sys::CancelRequestFn>(
request,
),
free: std::mem::transmute::<*mut std::ffi::c_void, sys::CancelFreeFn>(free),
}
})
}
}
struct Owned {
ptr: *mut sys::irgx_cancel,
vt: Vtable,
}
impl Drop for Owned {
fn drop(&mut self) {
unsafe { (self.vt.free)(self.ptr) };
}
}
unsafe impl Send for Owned {}
unsafe impl Sync for Owned {}
#[derive(Clone)]
pub struct CancelToken {
inner: Arc<Owned>,
}
impl CancelToken {
pub fn new() -> Result<Self> {
let vt = super::plane::cancellation().ok_or(Error::Uncancellable)?;
let mut ptr: *mut sys::irgx_cancel = std::ptr::null_mut();
let status = unsafe { (vt.new)(&raw mut ptr) };
if status < 0 || ptr.is_null() {
return Err(Error::Failed(
"irregex: the engine would not allocate a cancel token".into(),
));
}
Ok(Self {
inner: Arc::new(Owned { ptr, vt }),
})
}
pub fn cancel(&self) {
unsafe { (self.inner.vt.request)(self.inner.ptr) };
}
pub(super) fn raw(&self) -> *mut sys::irgx_cancel {
self.inner.ptr
}
}
impl std::fmt::Debug for CancelToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CancelToken")
.field("holders", &Arc::strong_count(&self.inner))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_token_crosses_threads() {
const fn assert<T: Send + Sync + Clone>() {}
assert::<CancelToken>();
}
#[test]
fn no_plane_is_an_answer_rather_than_a_useless_token() {
if super::super::plane::cancellation().is_none() {
assert!(matches!(CancelToken::new(), Err(Error::Uncancellable)));
}
}
#[test]
fn a_partial_trio_resolves_to_nothing() {
let only_new = Vtable::resolve(|name| {
(name == c"irgx_cancel_new").then_some(std::ptr::dangling_mut())
});
assert!(only_new.is_none());
}
}