use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::path::PathBuf;
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use std::time::{Duration, Instant};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::schema::Message;
pub trait Disposition: Serialize + DeserializeOwned + Send + 'static {}
pub trait Carried: Disposition {
fn carried() -> Self;
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
#[error("the inbox is closed: {reason}")]
pub struct Closed {
pub reason: String,
}
impl Closed {
#[must_use]
pub fn new(reason: impl Into<String>) -> Self {
Self {
reason: reason.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Undelivered {
#[error(transparent)]
Closed(Closed),
#[error(transparent)]
Backend(BackendError),
}
impl From<Closed> for Undelivered {
fn from(closed: Closed) -> Self {
Self::Closed(closed)
}
}
impl From<BackendError> for Undelivered {
fn from(failure: BackendError) -> Self {
Self::Backend(failure)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum BackendError {
#[error("{} is not {what}: {why}", path.display())]
Absent {
what: &'static str,
path: PathBuf,
why: String,
},
#[error(
"{} is already bound by another receiver; a spool has one receiver at a time",
path.display()
)]
Bound {
path: PathBuf,
},
#[error("{} is not a document this build reads: {why}", file.display())]
Unreadable {
at: PathBuf,
file: PathBuf,
why: String,
},
#[error(
"nothing took the message offered to the spool at {} within {}; it was withdrawn",
spool.display(),
wait_text(*waited)
)]
Elapsed {
spool: PathBuf,
waited: Duration,
},
#[error(
"the receiver of the spool at {} took the message ({}) and is no longer bound, with no answer written",
spool.display(),
file.display()
)]
Abandoned {
spool: PathBuf,
file: PathBuf,
},
#[error("the receiver of the spool at {} refused the message: {why}", spool.display())]
Refused {
spool: PathBuf,
why: String,
},
#[error("cannot {action} {}: {why}", path.display())]
Io {
action: &'static str,
path: PathBuf,
why: String,
},
#[error("the {what} does not serialize: {why}")]
Encoding {
what: &'static str,
why: String,
},
}
impl BackendError {
pub(crate) fn io(
action: &'static str,
path: &std::path::Path,
failure: &std::io::Error,
) -> Self {
Self::Io {
action,
path: path.to_path_buf(),
why: failure.to_string(),
}
}
}
fn wait_text(wait: Duration) -> String {
if wait.subsec_nanos() == 0 {
format!("{}s", wait.as_secs())
} else {
format!("{}ms", wait.as_millis())
}
}
pub(crate) type Answer<D> = Result<D, Closed>;
const LET_GO: &str = "the receiver took the message and let it go without answering";
const DROPPED: &str = "the inbox was dropped without being closed";
pub(crate) struct Reply<D> {
deliver: Option<Box<dyn FnOnce(Answer<D>) + Send>>,
}
impl<D> Reply<D> {
pub(crate) fn to(deliver: impl FnOnce(Answer<D>) + Send + 'static) -> Self {
Self {
deliver: Some(Box::new(deliver)),
}
}
pub(crate) fn nobody() -> Self {
Self { deliver: None }
}
fn answer(mut self, answer: Answer<D>) {
if let Some(deliver) = self.deliver.take() {
deliver(answer);
}
}
}
impl<D> Drop for Reply<D> {
fn drop(&mut self) {
if let Some(deliver) = self.deliver.take() {
deliver(Err(Closed::new(LET_GO)));
}
}
}
type CloseHook = Box<dyn FnOnce(&Closed) + Send>;
struct Queued<M, D> {
id: u64,
message: M,
reply: Reply<D>,
}
struct State<M, D> {
closed: Option<Closed>,
queue: VecDeque<Queued<M, D>>,
taken: HashMap<u64, Reply<D>>,
answered: Vec<Answered<M, D>>,
on_close: Vec<CloseHook>,
next: u64,
}
pub(crate) struct Shared<M, D> {
state: Mutex<State<M, D>>,
arrived: Condvar,
}
impl<M, D> Shared<M, D> {
fn new() -> Self {
Self {
state: Mutex::new(State {
closed: None,
queue: VecDeque::new(),
taken: HashMap::new(),
answered: Vec::new(),
on_close: Vec::new(),
next: 0,
}),
arrived: Condvar::new(),
}
}
fn lock(&self) -> MutexGuard<'_, State<M, D>> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn offer(&self, message: M, reply: Reply<D>) -> Result<(), Closed> {
let mut state = self.lock();
if let Some(closed) = state.closed.clone() {
drop(state);
reply.answer(Err(closed.clone()));
return Err(closed);
}
let id = state.next;
state.next += 1;
state.queue.push_back(Queued { id, message, reply });
drop(state);
self.arrived.notify_all();
Ok(())
}
pub(crate) fn offer_all<E>(
&self,
messages: Vec<M>,
commit: impl FnOnce() -> Result<(), E>,
) -> Result<usize, Result<Closed, E>> {
let mut state = self.lock();
if let Some(closed) = state.closed.clone() {
return Err(Ok(closed));
}
commit().map_err(Err)?;
let count = messages.len();
for message in messages {
let id = state.next;
state.next += 1;
state.queue.push_back(Queued {
id,
message,
reply: Reply::nobody(),
});
}
drop(state);
self.arrived.notify_all();
Ok(count)
}
pub(crate) fn closed(&self) -> Option<Closed> {
self.lock().closed.clone()
}
pub(crate) fn on_close(&self, hook: impl FnOnce(&Closed) + Send + 'static) {
let mut state = self.lock();
match state.closed.clone() {
Some(closed) => {
drop(state);
hook(&closed);
}
None => state.on_close.push(Box::new(hook)),
}
}
fn close(&self, closed: Closed) {
let mut state = self.lock();
if state.closed.is_some() {
return;
}
state.closed = Some(closed.clone());
let hooks = std::mem::take(&mut state.on_close);
let mut replies: Vec<Reply<D>> = state.queue.drain(..).map(|queued| queued.reply).collect();
replies.extend(state.taken.drain().map(|(_, reply)| reply));
drop(state);
self.arrived.notify_all();
for hook in hooks {
hook(&closed);
}
for reply in replies {
reply.answer(Err(closed.clone()));
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Answered<M, D> {
pub message: M,
pub disposition: D,
}
pub struct Inbox<M: Message, D: Disposition> {
shared: Arc<Shared<M, D>>,
}
impl<M: Message + Send + 'static, D: Disposition> Default for Inbox<M, D> {
fn default() -> Self {
Self::new()
}
}
impl<M: Message, D: Disposition> fmt::Debug for Inbox<M, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.shared.lock();
f.debug_struct("Inbox")
.field("closed", &state.closed)
.field("queued", &state.queue.len())
.field("taken", &state.taken.len())
.field("answered", &state.answered.len())
.finish()
}
}
impl<M: Message + Send + 'static, D: Disposition> Inbox<M, D> {
#[must_use]
pub fn new() -> Self {
Self {
shared: Arc::new(Shared::new()),
}
}
#[must_use]
pub fn sender(&self) -> Sender<M, D> {
Sender::over(InProcess {
shared: Arc::clone(&self.shared),
})
}
#[must_use]
pub fn take(&self) -> Option<Delivered<M, D>> {
let mut state = self.shared.lock();
self.pop(&mut state)
}
#[must_use]
pub fn take_within(&self, timeout: Duration) -> Option<Delivered<M, D>> {
let until = Instant::now().checked_add(timeout);
let mut state = self.shared.lock();
loop {
if let Some(delivered) = self.pop(&mut state) {
return Some(delivered);
}
if state.closed.is_some() {
return None;
}
state = match until {
Some(until) => {
let left = until.saturating_duration_since(Instant::now());
if left.is_zero() {
return None;
}
self.shared
.arrived
.wait_timeout(state, left)
.unwrap_or_else(PoisonError::into_inner)
.0
}
None => self
.shared
.arrived
.wait(state)
.unwrap_or_else(PoisonError::into_inner),
};
}
}
fn pop(&self, state: &mut State<M, D>) -> Option<Delivered<M, D>> {
let Queued { id, message, reply } = state.queue.pop_front()?;
state.taken.insert(id, reply);
Some(Delivered {
message,
claim: Claim {
id,
shared: Arc::clone(&self.shared),
settled: false,
},
})
}
pub(crate) fn shared(&self) -> Arc<Shared<M, D>> {
Arc::clone(&self.shared)
}
}
impl<M: Message, D: Disposition> Inbox<M, D> {
pub fn close(&self, reason: Closed) {
self.shared.close(reason);
}
#[must_use]
pub fn closed(&self) -> Option<Closed> {
self.shared.closed()
}
}
impl<M: Message + Clone, D: Disposition + Clone> Inbox<M, D> {
#[must_use]
pub fn answered(&self) -> Vec<Answered<M, D>> {
self.shared.lock().answered.clone()
}
}
impl<M: Message, D: Disposition> Drop for Inbox<M, D> {
fn drop(&mut self) {
self.shared.close(Closed::new(DROPPED));
}
}
pub struct Delivered<M: Message, D: Disposition> {
message: M,
claim: Claim<M, D>,
}
struct Claim<M, D> {
id: u64,
shared: Arc<Shared<M, D>>,
settled: bool,
}
impl<M, D> Drop for Claim<M, D> {
fn drop(&mut self) {
if self.settled {
return;
}
let reply = self.shared.lock().taken.remove(&self.id);
drop(reply);
}
}
impl<M: Message, D: Disposition> fmt::Debug for Delivered<M, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Delivered")
.field("id", &self.claim.id)
.finish_non_exhaustive()
}
}
impl<M: Message, D: Disposition> Delivered<M, D> {
#[must_use]
pub fn message(&self) -> &M {
&self.message
}
}
impl<M: Message, D: Disposition + Clone> Delivered<M, D> {
pub fn answer(self, disposition: D) {
let Self { message, mut claim } = self;
claim.settled = true;
let reply = {
let mut state = claim.shared.lock();
state.answered.push(Answered {
message,
disposition: disposition.clone(),
});
state.taken.remove(&claim.id)
};
if let Some(reply) = reply {
reply.answer(Ok(disposition));
}
}
}
pub trait InboxBackend<M, D>: Send + Sync {
fn send(&self, message: M) -> Result<D, Undelivered>;
}
pub struct Sender<M: Message, D: Disposition> {
backend: Arc<dyn InboxBackend<M, D>>,
}
impl<M: Message, D: Disposition> Clone for Sender<M, D> {
fn clone(&self) -> Self {
Self {
backend: Arc::clone(&self.backend),
}
}
}
impl<M: Message, D: Disposition> fmt::Debug for Sender<M, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sender").finish_non_exhaustive()
}
}
impl<M: Message, D: Disposition> Sender<M, D> {
#[must_use]
pub fn over(backend: impl InboxBackend<M, D> + 'static) -> Self {
Self {
backend: Arc::new(backend),
}
}
pub fn send(&self, message: M) -> Result<D, Undelivered> {
self.backend.send(message)
}
}
impl<M: Message + Send + 'static, D: Disposition> Sender<M, D> {
#[must_use]
pub fn channel() -> (Self, Inbox<M, D>) {
let inbox = Inbox::new();
(inbox.sender(), inbox)
}
}
pub struct InProcess<M: Message, D: Disposition> {
shared: Arc<Shared<M, D>>,
}
impl<M: Message + Send + 'static, D: Disposition> InboxBackend<M, D> for InProcess<M, D> {
fn send(&self, message: M) -> Result<D, Undelivered> {
let slot: Arc<(Mutex<Option<Answer<D>>>, Condvar)> = Arc::default();
let answered = Arc::clone(&slot);
self.shared.offer(
message,
Reply::to(move |answer| {
*answered.0.lock().unwrap_or_else(PoisonError::into_inner) = Some(answer);
answered.1.notify_all();
}),
)?;
let mut waiting = slot.0.lock().unwrap_or_else(PoisonError::into_inner);
loop {
if let Some(answer) = waiting.take() {
return answer.map_err(Undelivered::Closed);
}
waiting = slot.1.wait(waiting).unwrap_or_else(PoisonError::into_inner);
}
}
}