use std::sync::{Arc, Mutex};
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::System::IO::CancelIoEx;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RawHandle(isize);
impl RawHandle {
pub(crate) fn from_handle(handle: HANDLE) -> Self {
Self(handle.0 as isize)
}
pub(crate) fn as_handle(self) -> HANDLE {
HANDLE(self.0 as *mut core::ffi::c_void)
}
}
pub(crate) struct PipeHandle {
handle: RawHandle,
cancelled: Mutex<bool>,
}
impl PipeHandle {
pub(crate) fn new(handle: HANDLE) -> Arc<Self> {
Arc::new(Self {
handle: RawHandle::from_handle(handle),
cancelled: Mutex::new(false),
})
}
pub(crate) fn as_handle(&self) -> HANDLE {
self.handle.as_handle()
}
pub(crate) fn issue<T>(&self, start: impl FnOnce(HANDLE) -> T) -> Option<T> {
let cancelled = self.cancelled.lock().expect("pipe cancellation state");
if *cancelled {
return None;
}
Some(start(self.as_handle()))
}
pub(crate) fn cancel(&self) {
let mut cancelled = self.cancelled.lock().expect("pipe cancellation state");
*cancelled = true;
_ = unsafe { CancelIoEx(self.as_handle(), None) };
}
}
impl Drop for PipeHandle {
fn drop(&mut self) {
let handle = self.as_handle();
if handle.is_invalid() {
return;
}
_ = unsafe { CloseHandle(handle) };
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn round_trips_a_handle_value() {
let handle = HANDLE(0x1234 as *mut core::ffi::c_void);
assert_eq!(RawHandle::from_handle(handle).as_handle(), handle);
}
fn detached_pipe() -> Arc<PipeHandle> {
PipeHandle::new(HANDLE::default())
}
#[test]
fn issues_an_operation_while_live() {
assert_eq!(detached_pipe().issue(|_handle| 7), Some(7));
}
#[test]
#[cfg_attr(miri, ignore)] fn refuses_an_operation_started_after_cancellation() {
let pipe = detached_pipe();
pipe.cancel();
assert_eq!(pipe.issue(|_handle| 7), None);
}
}