use std::fmt;
use std::marker::PhantomData;
use std::time::Duration;
use crate::function::process::{IntoProcess, NoLink};
use crate::host::api::message;
use crate::host::{self};
use crate::serializer::{Bincode, DecodeError, Serializer};
use crate::{Process, ProcessConfig, Tag};
const LINK_DIED: u32 = 1;
const TIMEOUT: u32 = 9027;
pub struct Catching;
pub struct Mailbox<M, S = Bincode, L = ()>
where
S: Serializer<M>,
{
phantom: PhantomData<(M, S, L)>,
}
impl<M, S> Mailbox<M, S, ()>
where
S: Serializer<M>,
{
#[track_caller]
pub fn receive(&self) -> M {
self.receive_(&[], None).unwrap()
}
#[track_caller]
pub fn tag_receive(&self, tags: &[Tag]) -> M {
self.receive_(tags, None).unwrap()
}
pub fn catch_link_failure(self) -> Mailbox<M, S, Catching> {
unsafe {
host::api::process::die_when_link_dies(0);
Mailbox::<M, S, Catching>::new()
}
}
}
impl<M, S> Mailbox<M, S, Catching>
where
S: Serializer<M>,
{
pub fn receive(&self) -> MailboxResult<M> {
self.receive_(&[], None)
}
pub fn tag_receive(&self, tags: &[Tag]) -> MailboxResult<M> {
self.receive_(tags, None)
}
}
impl<M, S, L> Mailbox<M, S, L>
where
S: Serializer<M>,
{
pub fn this(&self) -> Process<M, S> {
Process::new(host::node_id(), host::process_id())
}
pub fn try_receive(&self, timeout: Duration) -> MailboxResult<M> {
self.receive_(&[], Some(timeout))
}
pub fn receive_timeout(&self, timeout: Duration) -> MailboxResult<M> {
self.receive_(&[], Some(timeout))
}
pub fn tag_receive_timeout(&self, tags: &[Tag], timeout: Duration) -> MailboxResult<M> {
self.receive_(tags, Some(timeout))
}
fn receive_(&self, tags: &[Tag], timeout: Option<Duration>) -> MailboxResult<M> {
let tags: Vec<i64> = tags.iter().map(|tag| tag.id()).collect();
let timeout_ms = match timeout {
Some(timeout) => timeout.as_millis() as u64,
None => u64::MAX,
};
let message_type = unsafe { message::receive(tags.as_ptr(), tags.len(), timeout_ms) };
match message_type {
LINK_DIED => MailboxResult::LinkDied(unsafe { Tag::from(message::get_tag()) }),
TIMEOUT => MailboxResult::TimedOut,
_ => match S::decode() {
Ok(msg) => MailboxResult::Message(msg),
Err(err) => MailboxResult::DeserializationFailed(err),
},
}
}
pub unsafe fn new() -> Self {
Self {
phantom: PhantomData {},
}
}
}
impl<M, S, L> Clone for Mailbox<M, S, L>
where
S: Serializer<M>,
{
fn clone(&self) -> Self {
Self {
phantom: self.phantom,
}
}
}
impl<M, S, L> Copy for Mailbox<M, S, L> where S: Serializer<M> {}
impl<M, S, L> fmt::Debug for Mailbox<M, S, L>
where
S: Serializer<M>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Mailbox")
.field("message", &std::any::type_name::<M>())
.field("serializer", &std::any::type_name::<S>())
.field("link", &std::any::type_name::<L>())
.finish()
}
}
#[derive(Debug)]
pub enum MailboxResult<T> {
Message(T),
DeserializationFailed(DecodeError),
TimedOut,
LinkDied(Tag),
}
impl<T> MailboxResult<T> {
#[track_caller]
pub fn unwrap(self) -> T {
match self {
MailboxResult::Message(msg) => msg,
MailboxResult::DeserializationFailed(err) => panic!("{:?}", err),
MailboxResult::TimedOut => panic!("TimedOut"),
MailboxResult::LinkDied(_) => panic!("LinkDied"),
}
}
pub fn is_message(&self) -> bool {
matches!(self, MailboxResult::Message(_))
}
pub fn is_link_died(&self) -> bool {
matches!(self, MailboxResult::LinkDied(_))
}
pub fn is_timed_out(&self) -> bool {
matches!(self, MailboxResult::TimedOut)
}
}
impl<M, S> NoLink for Mailbox<M, S> where S: Serializer<M> {}
impl<M, S> IntoProcess<M, S> for Mailbox<M, S>
where
S: Serializer<M>,
{
type Process = Process<M, S>;
fn spawn<C>(
capture: C,
entry: fn(C, Self),
link: Option<Tag>,
config: Option<&ProcessConfig>,
node: Option<u64>,
) -> Self::Process
where
S: Serializer<C> + Serializer<M>,
{
let entry = entry as usize as i32;
let node_id = node.unwrap_or_else(host::node_id);
match host::spawn(node, config, link, type_helper_wrapper::<C, M, S>, entry) {
Ok(id) => {
if std::mem::size_of::<C>() == 0 {
Process::new(node_id, id)
} else {
let child = Process::<C, S>::new(node_id, id);
child.send(capture);
unsafe { std::mem::transmute(child) }
}
}
Err(err) => panic!("Failed to spawn a process: {}", err),
}
}
}
fn type_helper_wrapper<C, M, S>(function: i32)
where
S: Serializer<C> + Serializer<M>,
{
let captured = if std::mem::size_of::<C>() == 0 {
unsafe { std::mem::MaybeUninit::<C>::zeroed().assume_init() }
} else {
unsafe { Mailbox::<C, S>::new() }.receive()
};
let mailbox = unsafe { Mailbox::new() };
let function: fn(C, Mailbox<M, S>) = unsafe { std::mem::transmute(function) };
function(captured, mailbox);
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use lunatic_test::test;
use super::*;
use crate::{sleep, Mailbox};
#[test]
fn mailbox() {
let child = Process::spawn(1, |capture, mailbox: Mailbox<i32>| {
assert_eq!(capture, 1);
assert_eq!(mailbox.receive(), 2);
});
child.send(2);
sleep(Duration::from_millis(100));
}
#[test]
#[should_panic]
fn mailbox_link() {
Process::spawn_link((), |_, _: Mailbox<()>| {
panic!("fails");
});
sleep(Duration::from_millis(100));
}
}