blob: ffa3ec810ebf90f79ae5dcf6b34b1dbdcf0dc213 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Single-threaded one-shot channel.
//!
//! Per [`crate::PipelineEntry`]: sender on entry, receiver on caller. Commit
//! handler fires via [`crate::PipelineEntry::take_reply_sender`]. Drop
//! without send -> `Err(Canceled)` (view-change cleanup via
//! [`crate::Pipeline::cancel_all_subscribers`]).
use futures_core::future::FusedFuture;
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::mem;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
/// `Sender` dropped without sending. Mirrors `futures::channel::oneshot::Canceled`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Canceled;
impl fmt::Display for Canceled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("oneshot sender dropped without sending")
}
}
impl std::error::Error for Canceled {}
enum State<T> {
Empty,
Waiting(Waker),
Ready(T),
/// Sender dropped; receiver resolves `Err(Canceled)` then `Done`.
SenderDropped,
/// Receiver dropped; `Sender::send` -> `Err(value)`.
ReceiverDropped,
/// Resolved. Re-polls return `Err(Canceled)` idempotently, prevents
/// a `select!`/`Fuse` re-poll from registering a waker nothing can fire.
Done,
}
struct Inner<T> {
state: RefCell<State<T>>,
}
/// Send half. Single-use; drop without send -> receiver gets `Err(Canceled)`.
pub struct Sender<T> {
inner: Rc<Inner<T>>,
}
/// Receive half. `Future<Output = Result<T, Canceled>>`. `FusedFuture`, safe
/// in `select!`/`Fuse`. `Unpin`, `.await` works without `pin!`.
pub struct Receiver<T> {
inner: Rc<Inner<T>>,
}
// Explicit Unpin: compile error if a future field becomes !Unpin.
impl<T> Unpin for Sender<T> {}
impl<T> Unpin for Receiver<T> {}
// Compile-time: Sender/Receiver are NEITHER Send NOR Sync. Pattern from
// `static_assertions::assert_not_impl_any!`: blanket `()` impl + marker
// impls gated on Send/Sync break inference if either is added.
//
// # Safety
// State is `Rc<RefCell<_>>`; thread-sharing would race at borrow.
const _: fn() = || {
trait AmbiguousIfImpl<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfImpl<()> for T {}
impl<T: ?Sized + Send> AmbiguousIfImpl<u8> for T {}
impl<T: ?Sized + Sync> AmbiguousIfImpl<u16> for T {}
let _ = <Sender<u64> as AmbiguousIfImpl<_>>::some_item;
let _ = <Receiver<u64> as AmbiguousIfImpl<_>>::some_item;
};
// Compile-time: both halves stay Unpin even when payload T is !Unpin.
const _: () = {
const fn assert_unpin<T: Unpin>() {}
struct NotUnpin {
_pinned: std::marker::PhantomPinned,
}
assert_unpin::<Sender<u64>>();
assert_unpin::<Receiver<u64>>();
assert_unpin::<Sender<NotUnpin>>();
assert_unpin::<Receiver<NotUnpin>>();
};
impl<T> fmt::Debug for Sender<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sender").finish_non_exhaustive()
}
}
impl<T> fmt::Debug for Receiver<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Receiver").finish_non_exhaustive()
}
}
/// New one-shot channel.
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let inner = Rc::new(Inner {
state: RefCell::new(State::Empty),
});
(
Sender {
inner: Rc::clone(&inner),
},
Receiver { inner },
)
}
impl<T> Sender<T> {
/// `true` iff `Receiver` dropped. No waker; on single-threaded runtime
/// `false` is valid until next `.await`. Sender stays usable.
#[must_use]
pub fn is_closed(&self) -> bool {
matches!(*self.inner.state.borrow(), State::ReceiverDropped)
}
/// Deliver `value`. Buffers in `State::Ready` until poll/drop.
/// Receiver-drop-without-poll silently drops the value; check
/// [`Self::is_closed`] first to avoid producing.
///
/// # Errors
/// `Err(value)` if receiver already dropped, caller recovers payload.
pub fn send(self, value: T) -> Result<(), T> {
let mut state = self.inner.state.borrow_mut();
match mem::replace(&mut *state, State::Empty) {
State::Empty => {
*state = State::Ready(value);
Ok(())
}
State::Waiting(waker) => {
*state = State::Ready(value);
drop(state);
waker.wake();
Ok(())
}
State::ReceiverDropped => {
*state = State::ReceiverDropped;
Err(value)
}
// Unreachable: Ready/SenderDropped/Done require Sender already
// consumed/dropped. Debug-assert + fall through to Err(value);
// future refactor violating invariant fails loudly in dev,
// stays receiver-gone-equivalent in prod.
other @ (State::Ready(_) | State::SenderDropped | State::Done) => {
let label = match &other {
State::Ready(_) => "Ready",
State::SenderDropped => "SenderDropped",
State::Done => "Done",
_ => unreachable!(),
};
debug_assert!(false, "oneshot::Sender::send: unreachable state {label}");
// Restore (mem::replace moved it out). Ready round-trips
// its buffered value so receiver still gets original.
*state = other;
Err(value)
}
}
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
let mut state = self.inner.state.borrow_mut();
match mem::replace(&mut *state, State::SenderDropped) {
State::Empty => {}
State::Waiting(waker) => {
drop(state);
waker.wake();
}
// send already terminated, or receiver dropped first,
// preserve terminal state.
other => *state = other,
}
}
}
impl<T> Future for Receiver<T> {
type Output = Result<T, Canceled>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<T, Canceled>> {
let mut state = self.inner.state.borrow_mut();
match mem::replace(&mut *state, State::Empty) {
State::Empty => {
*state = State::Waiting(cx.waker().clone());
Poll::Pending
}
State::Waiting(old) => {
let waker = if old.will_wake(cx.waker()) {
old
} else {
cx.waker().clone()
};
*state = State::Waiting(waker);
Poll::Pending
}
State::Ready(value) => {
// Transition to Done: re-polls return Err(Canceled), no
// dangling waker (sender gone).
*state = State::Done;
Poll::Ready(Ok(value))
}
State::SenderDropped | State::Done => {
*state = State::Done;
Poll::Ready(Err(Canceled))
}
// ReceiverDropped is set by Receiver::drop; can't be alive +
// dropped simultaneously.
State::ReceiverDropped => unreachable!(),
}
}
}
impl<T> FusedFuture for Receiver<T> {
fn is_terminated(&self) -> bool {
matches!(*self.inner.state.borrow(), State::Done)
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
// Replacing Ready(_) drops any undelivered value.
let _ = mem::replace(&mut *self.inner.state.borrow_mut(), State::ReceiverDropped);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sender_drop_while_waiting_wakes_with_canceled() {
let (tx, rx) = channel::<u64>();
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
assert!(fut.as_mut().poll(&mut cx).is_pending());
drop(tx);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)), got {other:?}"),
}
}
#[test]
fn ready_value_dropped_when_receiver_drops_unread() {
let sentinel = Rc::new(());
let weak = Rc::downgrade(&sentinel);
let (tx, rx) = channel::<Rc<()>>();
tx.send(sentinel).expect("receiver alive");
assert!(weak.upgrade().is_some());
drop(rx);
assert!(weak.upgrade().is_none(), "Ready dropped on receiver drop");
}
#[test]
fn is_closed_reflects_receiver_drop() {
let (tx, rx) = channel::<u64>();
assert!(!tx.is_closed());
drop(rx);
assert!(tx.is_closed());
// Sender stays usable; send returns Err(value).
match tx.send(99) {
Err(value) => assert_eq!(value, 99),
Ok(()) => panic!("expected Err after receiver drop"),
}
}
#[test]
fn is_closed_false_while_waiting() {
// is_closed flips only on receiver drop, never on Empty/Waiting.
let (tx, rx) = channel::<u64>();
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
assert!(fut.as_mut().poll(&mut cx).is_pending());
assert!(!tx.is_closed());
}
// Regression: select!/Fuse re-poll after Ready(Ok) must not register a
// waker (sender gone). Terminal Done guarantees Err(Canceled) idempotency
// so consumer can't stall indistinguishably from stuck consensus.
#[test]
fn poll_after_ready_ok_returns_ready_canceled() {
let (tx, rx) = channel::<u64>();
tx.send(42).expect("receiver alive");
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Ok(value)) => assert_eq!(value, 42),
other => panic!("expected Ready(Ok(42)), got {other:?}"),
}
// Second poll must NOT be Pending, Done state exists to prevent that deadlock.
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)) on re-poll, got {other:?}"),
}
// Third poll: still Ready(Err(Canceled)), not Pending.
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)) on third poll, got {other:?}"),
}
}
#[test]
fn poll_after_ready_canceled_stays_ready_canceled() {
let (tx, rx) = channel::<u64>();
drop(tx);
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)), got {other:?}"),
}
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)) on re-poll, got {other:?}"),
}
}
#[test]
fn is_terminated_lifecycle() {
let (tx, rx) = channel::<u64>();
assert!(!rx.is_terminated(), "fresh: not terminated");
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
assert!(fut.as_mut().poll(&mut cx).is_pending());
assert!(!fut.is_terminated(), "waiting: not terminated");
tx.send(7).expect("receiver alive");
assert!(
!fut.is_terminated(),
"Ready-but-unpolled: not terminated (select! must poll)"
);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Ok(7)) => {}
other => panic!("expected Ready(Ok(7)), got {other:?}"),
}
assert!(fut.is_terminated(), "post-delivery: terminated");
}
#[test]
fn is_terminated_after_sender_drop_is_observed() {
let (tx, rx) = channel::<u64>();
drop(tx);
// Pre-poll: SenderDropped visible, Canceled not yet seen.
assert!(!rx.is_terminated());
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(rx);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Err(Canceled)) => {}
other => panic!("expected Ready(Err(Canceled)), got {other:?}"),
}
assert!(fut.is_terminated(), "post-Canceled: terminated");
}
// No-pin call shape (works because Receiver: Unpin).
#[test]
fn receiver_polled_via_pin_new_directly() {
let (tx, rx) = channel::<u64>();
tx.send(123).expect("receiver alive");
let mut rx = rx;
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
match Pin::new(&mut rx).poll(&mut cx) {
Poll::Ready(Ok(value)) => assert_eq!(value, 123),
other => panic!("expected Ready(Ok(123)), got {other:?}"),
}
}
}