use super::envelope::IncomingEnvelope;
use super::session::SessionInner;
use super::{Error, Message};
use std::fmt;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex, Weak, mpsc};
#[cfg(any(test, feature = "fuzz"))]
use std::time::Duration;
use std::time::Instant;
pub struct Promise<T> {
result: mpsc::Receiver<Result<PromiseResult, Error>>,
notification: Arc<Mutex<NotificationState>>,
registered: bool,
value: PhantomData<fn() -> T>,
session: Weak<SessionInner>,
deadline: Instant,
#[cfg(any(test, feature = "fuzz"))]
wait_hook: Option<mpsc::Sender<()>>,
}
impl Promise<Message> {
pub fn wait<T>(self) -> Result<T, Error>
where
T: TryFrom<Message>,
Error: From<T::Error>,
{
T::try_from(self.wait_result()?.response()?).map_err(Error::from)
}
#[cfg(any(test, feature = "fuzz"))]
pub(super) fn wait_worker_result(self) -> Result<Message, Error> {
self.worker_result()?.response()
}
}
impl Promise<()> {
pub fn wait(self) -> Result<(), Error> {
self.wait_result()?.written()
}
#[cfg(any(test, feature = "fuzz"))]
pub(super) fn wait_worker_result(self) -> Result<(), Error> {
self.worker_result()?.written()
}
}
impl<T> Promise<T> {
pub(super) fn pair(
session: Weak<SessionInner>,
deadline: Instant,
response: bool,
) -> (ResultSender, Self) {
let (sender, result) = mpsc::sync_channel(1);
let notification = Arc::new(Mutex::new(NotificationState::default()));
(
ResultSender {
response,
result: sender,
notification: notification.clone(),
},
Self {
result,
notification,
registered: false,
value: PhantomData,
session,
deadline,
#[cfg(any(test, feature = "fuzz"))]
wait_hook: None,
},
)
}
pub fn notify<E: Copy + Send + 'static>(&mut self, sender: mpsc::Sender<E>, event: E) {
assert!(!self.registered, "promise notification already registered");
self.registered = true;
let mut notification = self.notification.lock().expect("notification not poisoned");
if notification.done {
let _ = sender.send(event);
} else {
notification.hook = Some(Box::new(move || {
let _ = sender.send(event);
}));
}
}
#[allow(unused_mut)] fn wait_result(mut self) -> Result<PromiseResult, Error> {
if let Some(session) = self.session.upgrade() {
session.expire();
}
#[cfg(any(test, feature = "fuzz"))]
if let Some(wait_hook) = self.wait_hook.take() {
let _ = wait_hook.send(());
}
loop {
match self
.result
.recv_timeout(self.deadline.saturating_duration_since(Instant::now()))
{
Ok(result) => return result,
Err(mpsc::RecvTimeoutError::Timeout) => {
if let Some(session) = self.session.upgrade() {
session.expire();
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
unreachable!("registered operation settles before its sender is released");
}
}
}
}
#[cfg(any(test, feature = "fuzz"))]
pub(super) fn watch_wait(&mut self) -> mpsc::Receiver<()> {
let (sender, receiver) = mpsc::channel();
self.wait_hook = Some(sender);
receiver
}
#[cfg(any(test, feature = "fuzz"))]
fn worker_result(self) -> Result<PromiseResult, Error> {
self.result
.recv_timeout(Duration::from_secs(5))
.expect("protocol worker must settle the promise")
}
}
impl<T> Drop for Promise<T> {
fn drop(&mut self) {
let hook = self
.notification
.lock()
.expect("notification not poisoned")
.hook
.take();
drop(hook);
}
}
impl<T> fmt::Debug for Promise<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut promise = f.debug_struct("Promise");
promise
.field("deadline", &self.deadline)
.field("registered", &self.registered);
if let Ok(notification) = self.notification.try_lock() {
promise.field("done", ¬ification.done);
}
promise.finish_non_exhaustive()
}
}
#[derive(Default)]
struct NotificationState {
done: bool,
hook: Option<Box<dyn FnOnce() + Send>>,
}
pub(super) struct ResultSender {
pub(super) response: bool,
result: mpsc::SyncSender<Result<PromiseResult, Error>>,
notification: Arc<Mutex<NotificationState>>,
}
impl ResultSender {
pub(super) fn send(
self,
result: Result<PromiseResult, Error>,
) -> Result<(), mpsc::SendError<Result<PromiseResult, Error>>> {
let mut notification = self.notification.lock().expect("notification not poisoned");
self.result.send(result)?;
notification.done = true;
if let Some(hook) = notification.hook.take() {
hook();
}
Ok(())
}
}
pub(super) enum PromiseResult {
Response(IncomingEnvelope),
Written,
}
impl PromiseResult {
fn response(self) -> Result<Message, Error> {
match self {
Self::Response(message) => message.decode(),
Self::Written => unreachable!("requests complete with responses"),
}
}
fn written(self) -> Result<(), Error> {
match self {
Self::Written => Ok(()),
Self::Response(_) => unreachable!("replies complete with write results"),
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{Error, Message, Promise, PromiseResult};
use std::fmt::Debug;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::{Weak, mpsc};
use std::time::{Duration, Instant};
#[test]
fn test_duplicate_notification() {
for completed in [false, true] {
let (sender, mut promise) = Promise::<()>::pair(Weak::new(), Instant::now(), false);
let (events, receiver) = mpsc::channel();
promise.notify(events.clone(), 1);
let sender = if completed {
assert!(sender.send(Ok(PromiseResult::Written)).is_ok());
None
} else {
Some(sender)
};
assert!(catch_unwind(AssertUnwindSafe(|| promise.notify(events, 2))).is_err());
if let Some(sender) = sender {
assert!(sender.send(Ok(PromiseResult::Written)).is_ok());
}
assert_eq!(receiver.try_recv(), Ok(1));
assert!(receiver.try_recv().is_err());
promise.wait().unwrap();
}
}
#[test]
fn test_disconnected_notification() {
for completed in [false, true] {
for success in [false, true] {
let (sender, mut promise) = Promise::<()>::pair(Weak::new(), Instant::now(), false);
let (events, receiver) = mpsc::channel();
drop(receiver);
let result = if success {
Ok(PromiseResult::Written)
} else {
Err(Error::Timeout)
};
if completed {
assert!(sender.send(result).is_ok());
promise.notify(events, 1);
} else {
promise.notify(events, 1);
assert!(sender.send(result).is_ok());
}
match promise.wait() {
Ok(()) => assert!(success),
Err(Error::Timeout) => assert!(!success),
result => panic!("unexpected result: {result:?}"),
}
}
}
}
#[test]
fn test_notification_with_waiter() {
for _ in 0..32 {
let (sender, mut promise) =
Promise::<()>::pair(Weak::new(), Instant::now() + Duration::from_secs(5), false);
let (events, receiver) = mpsc::channel();
promise.notify(events, 1);
let waiting = promise.watch_wait();
let waiter = std::thread::spawn(move || promise.wait());
waiting.recv_timeout(Duration::from_secs(5)).unwrap();
assert!(sender.send(Ok(PromiseResult::Written)).is_ok());
waiter.join().unwrap().unwrap();
assert_eq!(receiver.try_recv(), Ok(1));
assert!(receiver.try_recv().is_err());
}
}
#[test]
fn test_thread_capabilities() {
fn movable<T: Debug + Send + 'static>() {}
movable::<Promise<Message>>();
movable::<Promise<()>>();
}
}