use super::{CallError, CallReply};
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex, MutexGuard, mpsc};
type Answer = Result<CallReply, CallError>;
struct Entry {
answer: mpsc::SyncSender<Answer>,
wake: Arc<dyn Fn() + Send + Sync>,
}
#[derive(Default)]
struct State {
next: u64,
entries: BTreeMap<u64, Entry>,
closed: Option<CallError>,
}
pub struct PendingCalls {
bound: NonZeroUsize,
state: Mutex<State>,
}
impl std::fmt::Debug for PendingCalls {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
out.debug_struct("PendingCalls")
.field("bound", &self.bound)
.finish_non_exhaustive()
}
}
impl PendingCalls {
#[must_use]
pub fn new(bound: NonZeroUsize) -> Arc<Self> {
Arc::new(Self {
bound,
state: Mutex::default(),
})
}
fn lock(&self) -> Result<MutexGuard<'_, State>, CallError> {
self.state.lock().map_err(|_| CallError::Unavailable)
}
pub fn reserve(
self: &Arc<Self>,
wake: Arc<dyn Fn() + Send + Sync>,
) -> Result<PendingCall, CallError> {
let (answer, receiver) = mpsc::sync_channel(1);
let mut state = self.lock()?;
if let Some(reason) = state.closed {
return Err(reason);
}
if state.entries.len() >= self.bound.get() {
return Err(CallError::Capacity);
}
state.next = state.next.checked_add(1).ok_or(CallError::Unavailable)?;
let id = state.next;
state.entries.insert(id, Entry { answer, wake });
drop(state);
Ok(PendingCall {
id,
receiver,
owner: Arc::clone(self),
})
}
pub fn reply(&self, reply: CallReply) -> Result<bool, CallError> {
self.finish(reply.id(), Ok(reply))
}
pub fn refuse(&self, id: u64, reason: CallError) -> Result<bool, CallError> {
self.finish(id, Err(reason))
}
fn finish(&self, id: u64, answer: Answer) -> Result<bool, CallError> {
let entry = self.lock()?.entries.remove(&id);
let Some(entry) = entry else {
return Ok(false);
};
deliver(entry, answer)?;
Ok(true)
}
pub fn close(&self, reason: CallError) -> Result<(), CallError> {
let mut state = self.lock()?;
if state.closed.is_some() {
return Ok(());
}
state.closed = Some(reason);
let entries = std::mem::take(&mut state.entries);
drop(state);
let mut failure = None;
for entry in entries.into_values() {
if let Err(error) = deliver(entry, Err(reason)) {
failure = Some(error);
}
}
failure.map_or(Ok(()), Err)
}
pub fn count(&self) -> Result<usize, CallError> {
Ok(self.lock()?.entries.len())
}
}
fn deliver(entry: Entry, answer: Answer) -> Result<(), CallError> {
let Entry {
answer: sender,
wake,
} = entry;
match sender.try_send(answer) {
Ok(()) | Err(mpsc::TrySendError::Disconnected(_)) => {
wake();
Ok(())
}
Err(mpsc::TrySendError::Full(_)) => Err(CallError::Unavailable),
}
}
pub struct PendingCall {
id: u64,
receiver: mpsc::Receiver<Answer>,
owner: Arc<PendingCalls>,
}
impl std::fmt::Debug for PendingCall {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
out.debug_struct("PendingCall")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl PendingCall {
#[must_use]
pub const fn id(&self) -> u64 {
self.id
}
pub fn receive(&self) -> Answer {
self.receiver.recv().map_err(|_| CallError::Unavailable)?
}
pub fn ready(&self) -> Result<Option<Answer>, CallError> {
match self.receiver.try_recv() {
Ok(answer) => Ok(Some(answer)),
Err(mpsc::TryRecvError::Empty) => Ok(None),
Err(mpsc::TryRecvError::Disconnected) => Err(CallError::Unavailable),
}
}
}
impl Drop for PendingCall {
fn drop(&mut self) {
if let Err(error) = self.owner.refuse(self.id, CallError::Cancelled) {
tracing::error!(%error, "link call reservation retirement failed");
}
}
}