use std::{cell, marker, mem, os::fd};
use dope_sys::thread::{
Thread,
signal::{self, AcquireFault, ReleaseFault, Signal},
};
use o3::{base::marker::ThreadBound, mem::slot};
use crate::{Backend, Errno};
pub type Receiver = <Backend as Thread>::Receiver;
#[derive(Debug)]
pub enum Error<'owner, E> {
Acquire(Acquire),
Run(E),
Release {
run: Option<E>,
cause: Release<'owner>,
},
}
thread_local! {
static CLAIMED: cell::Cell<bool> = const { cell::Cell::new(false) };
}
#[derive(Debug)]
#[must_use = "the signal policy must be restored before another scope can start"]
pub struct Release<'owner> {
pub fault: ReleaseFault,
receiver: slot::Occupied<'owner, Receiver>,
}
impl Release<'_> {
pub fn retry(self) -> Result<(), Self> {
release(self.receiver)
}
}
fn release(receiver: slot::Occupied<'_, Receiver>) -> Result<(), Release<'_>> {
match signal::raw::Receiver::restore(&*receiver) {
Ok(()) => {
drop(receiver);
CLAIMED.with(|claimed| claimed.set(false));
Ok(())
}
Err(fault) => Err(Release { fault, receiver }),
}
}
type State<'process> = (
<Receiver as signal::raw::Receiver>::Source<'process>,
marker::PhantomData<(cell::Cell<&'process ()>, ThreadBound)>,
);
#[must_use]
pub struct Source<'process> {
read: State<'process>,
}
impl Signal for Source<'_> {
type Error = <Receiver as signal::raw::Receiver>::Wait;
fn wait(&self) -> Result<(), Self::Error> {
self.read.0.wait()
}
}
impl Source<'_> {
pub fn scope<'owner, R, E>(
retained: slot::Vacant<'owner, Receiver>,
run: impl for<'process> FnOnce(Source<'process>) -> Result<R, E>,
) -> Result<R, Error<'owner, E>> {
if !<Backend as Thread>::is_main() {
return Err(Error::Acquire(Acquire::Thread));
}
if CLAIMED.with(|claimed| claimed.replace(true)) {
return Err(Error::Acquire(Acquire::Busy));
}
let receiver = match unsafe { <Receiver as signal::raw::Receiver>::try_new() } {
Ok(receiver) => retained.insert(receiver),
Err(error) => {
CLAIMED.with(|claimed| claimed.set(false));
return Err(Error::Acquire(Acquire::from_native(error.into())));
}
};
let source = Source {
read: (
signal::raw::Receiver::source(&*receiver),
marker::PhantomData,
),
};
let outcome = run(source);
match release(receiver) {
Ok(()) => outcome.map_err(Error::Run),
Err(cause) => Err(Error::Release {
run: outcome.err(),
cause,
}),
}
}
}
impl fd::AsFd for Source<'_> {
fn as_fd(&self) -> fd::BorrowedFd<'_> {
self.read.0.as_fd()
}
}
const _: () = {
assert!(mem::size_of::<Source<'static>>() == mem::size_of::<fd::BorrowedFd<'static>>());
assert!(mem::align_of::<Source<'static>>() == mem::align_of::<fd::BorrowedFd<'static>>());
assert!(
mem::size_of::<Option<Source<'static>>>()
== mem::size_of::<Option<fd::BorrowedFd<'static>>>()
);
};
#[derive(Debug)]
pub enum Acquire {
Thread,
Busy,
MaskBlock(Errno),
SignalFd(Errno),
QueueOpen(Errno),
QueueCloexec(Errno),
Register(Errno),
Ignore(Errno),
}
impl Acquire {
fn from_native(error: AcquireFault) -> Self {
match error {
AcquireFault::MaskBlock(error) => Self::MaskBlock(error),
AcquireFault::SignalFd(error) => Self::SignalFd(error),
AcquireFault::QueueOpen(error) => Self::QueueOpen(error),
AcquireFault::QueueCloexec(error) => Self::QueueCloexec(error),
AcquireFault::Register(error) => Self::Register(error),
AcquireFault::Ignore(error) => Self::Ignore(error),
}
}
}