use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::pin::Pin;
use std::ptr::{self, NonNull};
use std::rc::Rc;
use std::sync::OnceLock;
use plurality::{Arc as PoolArc, coerce};
use crate::TypeErasedTask;
pub(crate) type TaskHandleSlot = OnceLock<ManuallyDrop<TaskHandleTemplate>>;
pub(crate) struct TaskHandleTemplate {
inner: Pin<PoolArc<dyn TypeErasedTask>>,
}
impl std::fmt::Debug for TaskHandleTemplate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaskHandleTemplate").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub(crate) struct TaskHandle {
inner: Pin<PoolArc<dyn TypeErasedTask>>,
_owner_thread: PhantomData<Rc<()>>,
}
impl TaskHandle {
pub(crate) fn new<T: TypeErasedTask + 'static>(inner: Pin<PoolArc<T>>) -> Self {
let handle = Self {
inner: PoolArc::unsize_pin(inner, coerce!(dyn TypeErasedTask)),
_owner_thread: PhantomData,
};
let template = ManuallyDrop::new(TaskHandleTemplate {
inner: unsafe { ptr::read(&raw const handle.inner) },
});
let initialized = handle.inner.as_ref().get_ref().handle_slot().set(template);
assert!(initialized.is_ok(), "task ownership must be initialized exactly once");
handle
}
pub(crate) fn as_ptr(&self) -> TaskPtr {
let template = self
.inner
.as_ref()
.get_ref()
.handle_slot()
.get()
.expect("task ownership is initialized before publishing task pointers");
TaskPtr {
template: NonNull::from(&**template),
}
}
pub(crate) fn into_raw(self) -> TaskPtr {
let pointer = self.as_ptr();
std::mem::forget(self);
pointer
}
#[must_use]
pub(crate) unsafe fn as_task(&self) -> Pin<&dyn TypeErasedTask> {
self.inner.as_ref()
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct TaskPtr {
template: NonNull<TaskHandleTemplate>,
}
impl TaskPtr {
pub(crate) unsafe fn increment_strong(self) {
std::mem::forget(unsafe { self.retain_remote() });
}
pub(crate) unsafe fn into_handle(self) -> TaskHandle {
let template = unsafe { self.template.as_ref() };
let inner = unsafe { ptr::read(&raw const template.inner) };
TaskHandle {
inner,
_owner_thread: PhantomData,
}
}
#[must_use]
pub(crate) unsafe fn as_task(&self) -> Pin<&dyn TypeErasedTask> {
unsafe { self.template.as_ref() }.inner.as_ref()
}
pub(crate) unsafe fn retain(self) -> TaskHandle {
let template = unsafe { self.template.as_ref() };
TaskHandle {
inner: template.inner.clone(),
_owner_thread: PhantomData,
}
}
pub(crate) unsafe fn retain_remote(self) -> RemoteTaskHandle {
let template = unsafe { self.template.as_ref() };
RemoteTaskHandle {
inner: template.inner.clone(),
}
}
pub(crate) unsafe fn into_remote(self) -> RemoteTaskHandle {
let template = unsafe { self.template.as_ref() };
let inner = unsafe { ptr::read(&raw const template.inner) };
RemoteTaskHandle { inner }
}
}
#[derive(Clone)]
pub(crate) struct RemoteTaskHandle {
inner: Pin<PoolArc<dyn TypeErasedTask>>,
}
impl RemoteTaskHandle {
pub(crate) unsafe fn as_task(&self) -> Pin<&dyn TypeErasedTask> {
self.inner.as_ref()
}
pub(crate) unsafe fn into_owner(self) -> TaskHandle {
TaskHandle {
inner: self.inner,
_owner_thread: PhantomData,
}
}
}
unsafe impl Send for TaskPtr {}
unsafe impl Sync for TaskPtr {}
impl PartialEq for TaskPtr {
fn eq(&self, other: &Self) -> bool {
self.template == other.template
}
}
impl Eq for TaskPtr {}
impl std::fmt::Debug for TaskHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TaskHandle").field(&self.as_ptr()).finish()
}
}
#[cfg(test)]
mod tests {
use plurality::MultiPool;
use static_assertions::assert_not_impl_any;
use super::*;
#[test]
fn owner_handle_is_thread_affine() {
assert_not_impl_any!(TaskHandle: Send, Sync);
}
#[test]
fn raw_ownership_and_diagnostics_preserve_identity() {
let pool = MultiPool::new();
let handle = crate::allocate_test_task_in(&pool);
let pointer = handle.as_ptr();
assert_eq!(format!("{handle:?}"), format!("TaskHandle({pointer:?})"));
let template = handle.inner.as_ref().get_ref().handle_slot().get().unwrap();
assert_eq!(format!("{:?}", **template), "TaskHandleTemplate { .. }");
let owned = handle.into_raw();
assert_eq!(owned, pointer);
drop(unsafe { owned.into_handle() });
assert!(pool.is_empty());
}
#[test]
fn different_task_pointers_are_not_equal() {
let pool = MultiPool::new();
let first = crate::allocate_test_task_in(&pool);
let second = crate::allocate_test_task_in(&pool);
assert_ne!(first.as_ptr(), second.as_ptr());
drop((first, second));
assert!(pool.is_empty());
}
#[test]
fn mixed_owners_release_the_allocation_exactly_once() {
let pool = MultiPool::new();
let original = crate::allocate_test_task_in(&pool);
let cloned = original.clone();
let owned = cloned.into_raw();
let retained = unsafe { owned.retain() };
drop(original);
assert_eq!(pool.len(), 1);
drop(unsafe { owned.into_handle() });
assert_eq!(pool.len(), 1);
assert_eq!(retained.as_ptr(), owned);
drop(retained);
assert!(pool.is_empty());
}
}