use std::{any::Any, cell::Cell, fmt, marker::PhantomData, rc::Rc};
use futures::{
SinkExt, StreamExt,
channel::mpsc,
future::{Either, LocalBoxFuture, select},
lock::Mutex,
};
use lenso_kernel::{
CancellationToken, InvocationContext, NativeStreamItem, NativeStreamSession, RuntimeFailure,
StreamCapability,
};
use crate::PluginResult;
#[derive(Debug)]
pub enum StreamInput<C: StreamCapability> {
Message(C::Message),
PeerHalfClosed,
}
enum ProviderOutput<C: StreamCapability> {
Message(C::Message),
PeerHalfClosed,
Terminal(Result<(), C::DomainError>),
Runtime(RuntimeFailure),
}
pub struct ProviderStreamChannel<C: StreamCapability> {
outgoing: mpsc::Sender<ProviderOutput<C>>,
incoming: mpsc::Receiver<StreamInput<C>>,
cancellation: CancellationToken,
send_closed: bool,
terminated: bool,
}
impl<C: StreamCapability> fmt::Debug for ProviderStreamChannel<C> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ProviderStreamChannel")
.field("capability", &C::ID)
.field("send_closed", &self.send_closed)
.field("terminated", &self.terminated)
.field("cancelled", &self.cancellation.is_cancelled())
.finish_non_exhaustive()
}
}
impl<C: StreamCapability> ProviderStreamChannel<C> {
pub async fn send(&mut self, message: C::Message) -> Result<(), RuntimeFailure> {
if self.send_closed || self.terminated {
return Err(RuntimeFailure::AdmissionClosed);
}
self.send_output(ProviderOutput::Message(message)).await
}
pub async fn close_send(&mut self) -> Result<(), RuntimeFailure> {
if self.send_closed || self.terminated {
return Err(RuntimeFailure::AdmissionClosed);
}
self.send_closed = true;
self.send_output(ProviderOutput::PeerHalfClosed).await
}
pub async fn finish(&mut self) -> Result<(), RuntimeFailure> {
self.terminate(ProviderOutput::Terminal(Ok(()))).await
}
pub async fn fail(&mut self, error: C::DomainError) -> Result<(), RuntimeFailure> {
self.terminate(ProviderOutput::Terminal(Err(error))).await
}
pub async fn fail_runtime(&mut self, error: RuntimeFailure) -> Result<(), RuntimeFailure> {
self.terminate(ProviderOutput::Runtime(error)).await
}
pub async fn complete(
mut self,
result: PluginResult<(), C::DomainError>,
) -> Result<(), RuntimeFailure> {
if !self.send_closed {
self.close_send().await?;
}
match result {
Ok(()) => self.finish().await,
Err(crate::PluginError::Domain(error)) => self.fail(error).await,
Err(crate::PluginError::Runtime(error)) => self.fail_runtime(error).await,
}
}
pub async fn receive(&mut self) -> Result<StreamInput<C>, RuntimeFailure> {
if self.cancellation.is_cancelled() {
return Err(RuntimeFailure::AdmissionClosed);
}
let receive = self.incoming.next();
futures::pin_mut!(receive);
match select(receive, self.cancellation.cancelled()).await {
Either::Left((Some(input), _)) => Ok(input),
Either::Left((None, _)) | Either::Right(_) => Err(RuntimeFailure::AdmissionClosed),
}
}
pub fn is_cancelled(&self) -> bool {
self.cancellation.is_cancelled()
}
async fn terminate(&mut self, output: ProviderOutput<C>) -> Result<(), RuntimeFailure> {
if self.terminated {
return Err(RuntimeFailure::AdmissionClosed);
}
self.terminated = true;
self.send_output(output).await
}
async fn send_output(&mut self, output: ProviderOutput<C>) -> Result<(), RuntimeFailure> {
if self.cancellation.is_cancelled() {
return Err(RuntimeFailure::AdmissionClosed);
}
let send = self.outgoing.send(output);
futures::pin_mut!(send);
match select(send, self.cancellation.cancelled()).await {
Either::Left((Ok(()), _)) => Ok(()),
Either::Left((Err(_), _)) | Either::Right(_) => Err(RuntimeFailure::AdmissionClosed),
}
}
}
pub struct ProviderStream<C: StreamCapability> {
incoming: mpsc::Sender<StreamInput<C>>,
outgoing: Rc<Mutex<mpsc::Receiver<ProviderOutput<C>>>>,
cancellation: CancellationToken,
consumer_send_closed: Rc<Cell<bool>>,
terminated: Rc<Cell<bool>>,
marker: PhantomData<fn() -> C>,
}
impl<C: StreamCapability> ProviderStream<C> {
pub fn channel(
context: &InvocationContext,
capacity: usize,
) -> (Self, ProviderStreamChannel<C>) {
let (incoming_sender, incoming_receiver) = mpsc::channel(capacity);
let (outgoing_sender, outgoing_receiver) = mpsc::channel(capacity);
let cancellation = context.cancellation();
(
Self {
incoming: incoming_sender,
outgoing: Rc::new(Mutex::new(outgoing_receiver)),
cancellation: cancellation.clone(),
consumer_send_closed: Rc::new(Cell::new(false)),
terminated: Rc::new(Cell::new(false)),
marker: PhantomData,
},
ProviderStreamChannel {
outgoing: outgoing_sender,
incoming: incoming_receiver,
cancellation,
send_closed: false,
terminated: false,
},
)
}
}
impl<C: StreamCapability> fmt::Debug for ProviderStream<C> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ProviderStream")
.field("capability", &C::ID)
.field("consumer_send_closed", &self.consumer_send_closed.get())
.field("terminated", &self.terminated.get())
.field("cancelled", &self.cancellation.is_cancelled())
.finish_non_exhaustive()
}
}
impl<C: StreamCapability> NativeStreamSession for ProviderStream<C> {
fn send(&self, message: Box<dyn Any>) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {
if self.cancellation.is_cancelled() || self.terminated.get() {
return Box::pin(futures::future::ready(Err(RuntimeFailure::AdmissionClosed)));
}
if self.consumer_send_closed.get() {
return Box::pin(futures::future::ready(Err(
RuntimeFailure::ProtocolViolation { capability: C::ID },
)));
}
let Ok(message) = message.downcast::<C::Message>() else {
return Box::pin(futures::future::ready(Err(
RuntimeFailure::ProtocolViolation { capability: C::ID },
)));
};
let mut incoming = self.incoming.clone();
let cancellation = self.cancellation.clone();
Box::pin(async move {
let send = incoming.send(StreamInput::Message(*message));
futures::pin_mut!(send);
match select(send, cancellation.cancelled()).await {
Either::Left((Ok(()), _)) => Ok(()),
Either::Left((Err(_), _)) | Either::Right(_) => {
Err(RuntimeFailure::AdmissionClosed)
}
}
})
}
fn receive(&self) -> LocalBoxFuture<'static, Result<NativeStreamItem, RuntimeFailure>> {
if self.cancellation.is_cancelled() || self.terminated.get() {
return Box::pin(futures::future::ready(Err(RuntimeFailure::AdmissionClosed)));
}
let outgoing = Rc::clone(&self.outgoing);
let cancellation = self.cancellation.clone();
let terminated = Rc::clone(&self.terminated);
Box::pin(async move {
let receive = async move { outgoing.lock().await.next().await };
futures::pin_mut!(receive);
match select(receive, cancellation.cancelled()).await {
Either::Left((Some(ProviderOutput::Message(message)), _)) => {
Ok(NativeStreamItem::Message(Box::new(message)))
}
Either::Left((Some(ProviderOutput::PeerHalfClosed), _)) => {
Ok(NativeStreamItem::PeerHalfClosed)
}
Either::Left((Some(ProviderOutput::Terminal(result)), _)) => {
terminated.set(true);
Ok(NativeStreamItem::Terminal(
result.map_err(|error| Box::new(error) as Box<dyn Any>),
))
}
Either::Left((Some(ProviderOutput::Runtime(error)), _)) => {
terminated.set(true);
Err(error)
}
Either::Left((None, _)) => Err(RuntimeFailure::PluginFailure {
detail: format!("provider Stream {} ended without a terminal outcome", C::ID),
}),
Either::Right(_) => Err(RuntimeFailure::AdmissionClosed),
}
})
}
fn close_send(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {
if self.cancellation.is_cancelled() || self.terminated.get() {
return Box::pin(futures::future::ready(Err(RuntimeFailure::AdmissionClosed)));
}
if self.consumer_send_closed.replace(true) {
return Box::pin(futures::future::ready(Err(
RuntimeFailure::ProtocolViolation { capability: C::ID },
)));
}
let mut incoming = self.incoming.clone();
let cancellation = self.cancellation.clone();
Box::pin(async move {
let send = incoming.send(StreamInput::PeerHalfClosed);
futures::pin_mut!(send);
match select(send, cancellation.cancelled()).await {
Either::Left((Ok(()), _)) => Ok(()),
Either::Left((Err(_), _)) | Either::Right(_) => {
Err(RuntimeFailure::AdmissionClosed)
}
}
})
}
fn cancel(&self) {
self.cancellation.cancel();
}
}