use crate::{Actor, ActorRef, SendError};
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
const PENDING: u8 = 0;
const REPLIED: u8 = 1;
const ABANDONED: u8 = 2;
struct ReplyCell<R> {
state: AtomicU8,
slot: UnsafeCell<MaybeUninit<R>>,
}
impl<R> ReplyCell<R> {
fn new() -> Self {
Self {
state: AtomicU8::new(PENDING),
slot: UnsafeCell::new(MaybeUninit::uninit()),
}
}
}
pub struct Responder<R> {
cell: *const ReplyCell<R>,
}
unsafe impl<R: Send> Send for Responder<R> {}
impl<R> Responder<R> {
pub fn reply(self, value: R) {
unsafe {
(*(*self.cell).slot.get()).write(value);
(*self.cell).state.store(REPLIED, Ordering::Release);
}
std::mem::forget(self);
}
}
impl<R> Drop for Responder<R> {
fn drop(&mut self) {
unsafe {
let _ = (*self.cell).state.compare_exchange(
PENDING,
ABANDONED,
Ordering::Release,
Ordering::Relaxed,
);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AskError {
NoReply,
Closed,
WouldBlockCallingCore,
}
impl<A: Actor> ActorRef<A> {
pub fn ask<R>(&self, make_msg: impl FnOnce(Responder<R>) -> A::Message) -> Result<R, AskError>
where
R: Send + 'static,
{
if self.would_deadlock_calling_core() {
return Err(AskError::WouldBlockCallingCore);
}
let cell = ReplyCell::<R>::new();
let responder = Responder { cell: &cell };
let msg = make_msg(responder);
if let Err(SendError::Closed(_)) = self.send_blocking(msg) {
return Err(AskError::Closed);
}
let mut waited: u32 = 0;
loop {
match cell.state.load(Ordering::Acquire) {
REPLIED => {
let value = unsafe { (*cell.slot.get()).assume_init_read() };
return Ok(value);
}
ABANDONED => return Err(AskError::NoReply),
_ => {
if waited < 256 {
std::hint::spin_loop();
} else if waited < 512 {
std::thread::yield_now();
} else {
std::thread::sleep(Duration::from_micros(20));
}
waited = waited.saturating_add(1);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::System;
#[test]
fn ask_returns_reply() {
struct Doubler;
struct Req(u64, Responder<u64>);
impl Actor for Doubler {
type Message = Req;
fn handle(&mut self, req: Req) {
let Req(n, resp) = req;
resp.reply(n * 2);
}
}
let sys = System::with_cores(1);
let addr = sys.spawn_on(0, Doubler);
for n in 0..1000u64 {
let r = addr.ask(|resp| Req(n, resp)).unwrap();
assert_eq!(r, n * 2);
}
drop(addr);
sys.shutdown();
}
#[test]
fn ask_across_cores() {
struct Echo;
impl Actor for Echo {
type Message = Responder<&'static str>;
fn handle(&mut self, resp: Responder<&'static str>) {
resp.reply("pong");
}
}
let sys = System::with_cores(3);
let addr = sys.spawn_on(2, Echo); assert_eq!(addr.ask(|resp| resp).unwrap(), "pong");
drop(addr);
sys.shutdown();
}
#[test]
fn ask_errors_when_actor_drops_without_reply() {
struct Rude;
impl Actor for Rude {
type Message = Responder<u64>;
fn handle(&mut self, _resp: Responder<u64>) {
}
}
let sys = System::with_cores(1);
let addr = sys.spawn_on(0, Rude);
assert_eq!(addr.ask(|resp| resp), Err(AskError::NoReply));
drop(addr);
sys.shutdown();
}
}