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};
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(&mut self, callback: impl FnOnce() + Send + 'static) {
assert!(!self.registered, "promise notification already registered");
self.registered = true;
let mut notification = self.notification.lock().expect("notification not poisoned");
if notification.done {
drop(notification);
callback();
} else {
notification.hook = Some(Box::new(callback));
}
}
#[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(());
}
match self.result.recv() {
Ok(result) => result,
Err(mpsc::RecvError) => {
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(test)]
pub(super) fn notification_unlocked(&self) -> impl Fn() -> bool + Send + 'static {
let notification = self.notification.clone();
move || notification.try_lock().is_ok()
}
#[cfg(any(test, feature = "fuzz"))]
fn worker_result(self) -> Result<PromiseResult, Error> {
self.result
.recv()
.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>) -> Notification {
let mut notification = self.notification.lock().expect("notification not poisoned");
let delivered = self.result.send(result).is_ok();
if delivered {
notification.done = true;
}
Notification {
delivered,
callback: notification.hook.take(),
}
}
}
#[must_use = "collect the notification and run it outside all wire locks"]
pub(super) struct Notification {
pub(super) delivered: bool,
callback: Option<Box<dyn FnOnce() + Send>>,
}
impl Notification {
pub(super) fn run(self) {
if let Some(callback) = self.callback {
callback();
}
}
}
#[derive(Default)]
pub(super) struct Notifications {
pending: Vec<Notification>,
}
impl Notifications {
pub(super) fn push(&mut self, notification: Notification) {
if notification.callback.is_some() {
self.pending.push(notification);
}
}
pub(super) fn is_empty(&self) -> bool {
self.pending.is_empty()
}
}
impl Drop for Notifications {
fn drop(&mut self) {
for notification in self.pending.drain(..) {
notification.run();
}
}
}
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, NotificationState, Promise, PromiseResult};
use darkbio_clock::TestClock;
use std::fmt::Debug;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::{Arc, Mutex, Weak, mpsc};
use std::time::Duration;
#[test]
fn test_duplicate_notification() {
for completed in [false, true] {
let tester = TestClock::new();
let (sender, mut promise) =
Promise::<()>::pair(Weak::new(), tester.clock().now(), false);
let (events, receiver) = mpsc::channel();
promise.notify(move || {
let _ = events.send(1);
});
let sender = if completed {
sender.send(Ok(PromiseResult::Written)).run();
None
} else {
Some(sender)
};
assert!(
catch_unwind(AssertUnwindSafe(
|| promise.notify(|| panic!("duplicate callback ran"))
))
.is_err()
);
if let Some(sender) = sender {
sender.send(Ok(PromiseResult::Written)).run();
}
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 tester = TestClock::new();
let (sender, mut promise) =
Promise::<()>::pair(Weak::new(), tester.clock().now(), false);
let (events, receiver) = mpsc::channel();
drop(receiver);
let result = if success {
Ok(PromiseResult::Written)
} else {
Err(Error::Timeout)
};
if completed {
sender.send(result).run();
promise.notify(move || {
let _ = events.send(1);
});
} else {
promise.notify(move || {
let _ = events.send(1);
});
sender.send(result).run();
}
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 tester = TestClock::new();
let (sender, mut promise) = Promise::<()>::pair(
Weak::new(),
tester.clock().now() + Duration::from_secs(5),
false,
);
let (events, receiver) = mpsc::channel();
promise.notify(move || {
let _ = events.send(1);
});
let waiting = promise.watch_wait();
let waiter = std::thread::spawn(move || promise.wait());
waiting.recv().unwrap();
let notification = sender.send(Ok(PromiseResult::Written));
waiter.join().unwrap().unwrap();
notification.run();
assert_eq!(receiver.try_recv(), Ok(1));
assert!(receiver.try_recv().is_err());
}
}
#[test]
fn test_dropped_promise_releases_callback_without_lock() {
struct Capture {
notification: Arc<Mutex<NotificationState>>,
dropped: mpsc::Sender<bool>,
}
impl Drop for Capture {
fn drop(&mut self) {
let _ = self.dropped.send(self.notification.try_lock().is_ok());
}
}
let tester = TestClock::new();
let (sender, mut promise) = Promise::<()>::pair(Weak::new(), tester.clock().now(), false);
let (dropped, observed) = mpsc::channel();
let capture = Capture {
notification: promise.notification.clone(),
dropped,
};
let (ran, running) = mpsc::channel();
promise.notify(move || {
let _ = ran.send(());
drop(capture);
});
drop(promise);
assert_eq!(observed.try_recv(), Ok(true));
assert!(running.try_recv().is_err());
let notification = sender.send(Ok(PromiseResult::Written));
assert!(!notification.delivered);
notification.run();
assert!(running.try_recv().is_err());
assert!(observed.try_recv().is_err());
}
#[test]
fn test_thread_capabilities() {
fn movable<T: Debug + Send + 'static>() {}
movable::<Promise<Message>>();
movable::<Promise<()>>();
}
}