use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, ready};
use anyhow::{Result, anyhow};
use bytes::Bytes;
use futures::FutureExt;
use serde::de::DeserializeOwned;
use tokio::sync::oneshot;
use super::super::ActiveMessageClient;
use crate::messenger::common::responses::{ResponseAwaiter, ResponseId};
use crate::observability::ClientResolution;
use crate::transports::{AdmissionState, SendAdmission, SendOutcome};
pub(super) type AdmissionReport = std::result::Result<(), String>;
enum AdmissionStage {
Admitted,
Failed(String),
Gated(SendAdmission),
Detached(oneshot::Receiver<AdmissionReport>),
}
impl AdmissionStage {
fn state(&self) -> AdmissionState {
match self {
Self::Admitted => AdmissionState::Admitted,
Self::Failed(_) => AdmissionState::Failed,
Self::Gated(admission) => admission.state(),
Self::Detached(_) => AdmissionState::Pending,
}
}
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
loop {
match self {
Self::Admitted => return Poll::Ready(Ok(())),
Self::Failed(reason) => return Poll::Ready(Err(anyhow!("{reason}"))),
Self::Gated(admission) => match Pin::new(admission).poll(cx) {
Poll::Ready(Ok(())) => *self = Self::Admitted,
Poll::Ready(Err(error)) => {
*self = Self::Failed(format!("Send failed: {error}"))
}
Poll::Pending => return Poll::Pending,
},
Self::Detached(receiver) => match Pin::new(receiver).poll(cx) {
Poll::Ready(Ok(report)) => {
*self = match report {
Ok(()) => Self::Admitted,
Err(reason) => Self::Failed(reason),
}
}
Poll::Ready(Err(_)) => {
*self = Self::Failed(
"Send failed: the send task ended without reporting".to_string(),
)
}
Poll::Pending => return Poll::Pending,
},
}
}
}
}
pub(super) struct Dispatched {
admission: AdmissionStage,
awaiter: Option<ResponseAwaiter>,
}
impl Dispatched {
pub(super) fn issued(outcome: SendOutcome, awaiter: ResponseAwaiter) -> Self {
let admission = match outcome {
SendOutcome::Admitted => AdmissionStage::Admitted,
SendOutcome::Pending(admission) => AdmissionStage::Gated(admission),
};
Self {
admission,
awaiter: Some(awaiter),
}
}
pub(super) fn detached(
receiver: oneshot::Receiver<AdmissionReport>,
awaiter: ResponseAwaiter,
) -> Self {
Self {
admission: AdmissionStage::Detached(receiver),
awaiter: Some(awaiter),
}
}
pub(super) fn failed(error: impl std::fmt::Display) -> Self {
Self {
admission: AdmissionStage::Failed(error.to_string()),
awaiter: None,
}
}
fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Bytes>>> {
if let Err(error) = ready!(self.admission.poll(cx)) {
self.awaiter = None;
return Poll::Ready(Err(error));
}
let Some(awaiter) = self.awaiter.as_mut() else {
return Poll::Ready(Err(anyhow!("send result polled after completion")));
};
match awaiter.poll_recv(cx) {
Poll::Ready(result) => {
self.awaiter = None;
Poll::Ready(result.map_err(|e| anyhow!(e)))
}
Poll::Pending => Poll::Pending,
}
}
fn poll_fire(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
if let Err(error) = ready!(self.admission.poll(cx)) {
self.awaiter = None;
return Poll::Ready(Err(error));
}
let probe = self
.awaiter
.as_mut()
.and_then(|awaiter| awaiter.recv().now_or_never());
self.awaiter = None;
match probe {
Some(Err(error)) => Poll::Ready(Err(anyhow!("Send failed: {error}"))),
_ => Poll::Ready(Ok(())),
}
}
}
pub(super) enum SendStage {
Acquiring(futures::future::BoxFuture<'static, Dispatched>),
Dispatched(Dispatched),
}
impl SendStage {
pub(super) fn failed(error: impl std::fmt::Display) -> Self {
SendStage::Dispatched(Dispatched::failed(error))
}
fn admission_state(&self) -> AdmissionState {
match self {
SendStage::Acquiring(_) => AdmissionState::Pending,
SendStage::Dispatched(dispatched) => dispatched.admission.state(),
}
}
fn poll_dispatched(&mut self, cx: &mut Context<'_>) -> Poll<&mut Dispatched> {
if let SendStage::Acquiring(fut) = self {
match fut.as_mut().poll(cx) {
Poll::Ready(dispatched) => *self = SendStage::Dispatched(dispatched),
Poll::Pending => return Poll::Pending,
}
}
match self {
SendStage::Dispatched(dispatched) => Poll::Ready(dispatched),
SendStage::Acquiring(_) => unreachable!("the branch above replaced Acquiring"),
}
}
fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Bytes>>> {
ready!(self.poll_dispatched(cx)).poll_response(cx)
}
fn poll_fire(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
ready!(self.poll_dispatched(cx)).poll_fire(cx)
}
}
pub struct Admitted<'a> {
stage: &'a mut SendStage,
}
impl Future for Admitted<'_> {
type Output = Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let stage = &mut *self.get_mut().stage;
ready!(stage.poll_dispatched(cx)).admission.poll(cx)
}
}
macro_rules! admission_api {
($result:ty $(, $param:ident)?) => {
impl$(<$param>)? $result {
pub fn admission_state(&self) -> AdmissionState {
self.stage.admission_state()
}
pub fn admitted(&mut self) -> Admitted<'_> {
Admitted {
stage: &mut self.stage,
}
}
}
};
}
pub struct FireResult {
pub(super) stage: SendStage,
}
impl Future for FireResult {
type Output = Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.stage.poll_fire(cx)
}
}
pub struct SyncResult {
pub(super) stage: SendStage,
}
impl Future for SyncResult {
type Output = Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.stage.poll_response(cx).map(|r| r.map(|_| ()))
}
}
pub struct UnaryResult {
pub(super) stage: SendStage,
}
impl Future for UnaryResult {
type Output = Result<Bytes>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.stage
.poll_response(cx)
.map(|r| r.map(|b| b.unwrap_or_default()))
}
}
pub struct TypedUnaryResult<R> {
pub(super) stage: SendStage,
pub(super) _marker: std::marker::PhantomData<R>,
}
impl<R> Unpin for TypedUnaryResult<R> {}
impl<R: DeserializeOwned> Future for TypedUnaryResult<R> {
type Output = Result<R>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.stage.poll_response(cx).map(|r| match r {
Ok(Some(bytes)) => serde_json::from_slice(&bytes)
.map_err(|e| anyhow!("Failed to deserialize response: {}", e)),
Ok(None) => Err(anyhow!("Expected response data, got empty")),
Err(e) => Err(e),
})
}
}
admission_api!(FireResult);
admission_api!(SyncResult);
admission_api!(UnaryResult);
admission_api!(TypedUnaryResult<R>, R);
pub(super) async fn drive_send_outcome(
client: &ActiveMessageClient,
send_result: Result<SendOutcome>,
response_id: ResponseId,
path_description: &'static str,
) -> AdmissionReport {
let error = match send_result {
Ok(SendOutcome::Admitted) => return Ok(()),
Ok(SendOutcome::Pending(admission)) => match admission.await {
Ok(()) => return Ok(()),
Err(error) => anyhow!(error),
},
Err(error) => error,
};
tracing::error!(
target: "crate::messenger::client",
error = %error,
path = path_description,
"Failed to send message"
);
if let Some(metrics) = client.observability.as_ref() {
metrics.record_client_resolution(ClientResolution::SendError);
}
let reason = format!("Send failed: {}", error);
let _ = client
.response_manager
.complete_outcome(response_id, Err(reason.clone()));
Err(reason)
}