use core::ffi::c_void;
use std::io;
use std::mem::ManuallyDrop;
use std::ptr;
use windows_sys::Win32::Foundation::{FALSE, TRUE};
use windows_sys::Win32::System::Threading::{
CloseThreadpoolWork, CreateThreadpoolWork, PTP_CALLBACK_INSTANCE, PTP_WORK,
SubmitThreadpoolWork, WaitForThreadpoolWorkCallbacks,
};
use crate::callback_env::CallbackEnviron;
struct WorkContext {
f: Box<dyn Fn() + Send + Sync + 'static>,
obligation: crate::obligation::CloseObligation,
#[allow(dead_code)]
registration: crate::heal::Registration,
}
unsafe extern "system" fn work_trampoline(
_instance: PTP_CALLBACK_INSTANCE,
context: *mut core::ffi::c_void,
_work: PTP_WORK,
) {
let ctx = unsafe { &*(context as *const WorkContext) };
crate::trace_record!("work", "trampoline-entered", _work);
(ctx.f)();
crate::trace_record!("work", "trampoline-left", _work);
}
pub struct ThreadpoolWork {
handle: PTP_WORK,
ctx: *mut WorkContext,
}
unsafe impl Send for ThreadpoolWork {}
unsafe impl Sync for ThreadpoolWork {}
impl ThreadpoolWork {
pub fn new<F>(callback: F, env: Option<&mut CallbackEnviron>) -> io::Result<Self>
where
F: Fn() + Send + Sync + 'static,
{
let registration = crate::heal::register(crate::heal::key_of(env.as_deref()));
let ctx = Box::into_raw(Box::new(WorkContext {
f: Box::new(callback),
obligation: crate::obligation::CloseObligation::new(),
registration,
}));
let env_ptr = env.map_or(ptr::null_mut(), |e| e.as_mut_ptr());
let handle = crate::trace_call!("CreateThreadpoolWork", 0, 0, {
unsafe { CreateThreadpoolWork(Some(work_trampoline), ctx.cast(), env_ptr.cast_const()) }
});
if handle == 0 {
let error = io::Error::last_os_error();
unsafe { drop(Box::from_raw(ctx)) };
return Err(error);
}
crate::trace_record!("work", "created", handle);
Ok(Self { handle, ctx })
}
pub fn submit(&self) {
unsafe { &*self.ctx }.obligation.record_live_before(|| {
crate::trace_call!("SubmitThreadpoolWork", self.handle, 0, {
unsafe { SubmitThreadpoolWork(self.handle) };
});
crate::trace_record!("work", "submitted", self.handle);
});
}
pub fn wait(&self) {
crate::trace_call!("WaitForThreadpoolWorkCallbacks", self.handle, 0, {
unsafe { WaitForThreadpoolWorkCallbacks(self.handle, FALSE) };
});
unsafe { &*self.ctx }.obligation.record_settled();
}
pub fn stop_and_drain(&self) {
self.wait();
}
pub fn cancel_pending(&self) {
crate::trace_call!("WaitForThreadpoolWorkCallbacks(cancel)", self.handle, 1, {
unsafe { WaitForThreadpoolWorkCallbacks(self.handle, TRUE) };
});
unsafe { &*self.ctx }.obligation.record_settled();
}
pub(crate) fn into_parts(self) -> (PTP_WORK, *mut c_void) {
let this = ManuallyDrop::new(self);
(this.handle, this.ctx.cast())
}
pub(crate) unsafe fn drop_context(context: *mut c_void) {
drop(unsafe { Box::from_raw(context.cast::<WorkContext>()) });
}
#[cfg(test)]
pub(crate) fn obligation_owed(&self) -> bool {
unsafe { &*self.ctx }.obligation.is_owed()
}
}
impl Drop for ThreadpoolWork {
fn drop(&mut self) {
crate::trace_record!("work", "drop-begin", self.handle);
let owed = unsafe { &*self.ctx }.obligation.is_owed();
if owed {
crate::trace_record!("work", crate::obligation::DROP_OBLIGATION_OWED, self.handle);
}
crate::trace_call!("WaitForThreadpoolWorkCallbacks", self.handle, 0, {
unsafe { WaitForThreadpoolWorkCallbacks(self.handle, FALSE) };
});
crate::trace_record!("work", "drop-drained", self.handle);
crate::trace_call!("CloseThreadpoolWork", self.handle, 0, {
unsafe { CloseThreadpoolWork(self.handle) };
});
unsafe { drop(Box::from_raw(self.ctx)) };
crate::trace_record!("work", "drop-closed", self.handle);
crate::obligation::fail_fast_if_owed(owed, "ThreadpoolWork", "stop_and_drain");
}
}
#[cfg(test)]
mod tests;