mirror of https://github.com/Trivernis/bromine.git
Add asynchronous response streams
Signed-off-by: trivernis <trivernis@protonmail.com>pull/32/head
parent
619a0173f0
commit
9cc7d1ffe8
@ -0,0 +1,150 @@
|
|||||||
|
use crate::context::Context;
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use crate::event::{Event, EventType};
|
||||||
|
use crate::ipc::stream_emitter::emit_metadata::EmitMetadata;
|
||||||
|
use crate::ipc::stream_emitter::emit_metadata_with_response::remove_reply_listener;
|
||||||
|
use crate::payload::IntoPayload;
|
||||||
|
use crate::poll_unwrap;
|
||||||
|
use futures_core::Stream;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::Poll;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
/// A metadata object returned after waiting for a reply to an event
|
||||||
|
/// This object needs to be awaited for to get the actual reply
|
||||||
|
pub struct EmitMetadataWithResponseStream<P: IntoPayload> {
|
||||||
|
pub(crate) timeout: Option<Duration>,
|
||||||
|
pub(crate) fut: Option<Pin<Box<dyn Future<Output = Result<ResponseStream>> + Send + Sync>>>,
|
||||||
|
pub(crate) emit_metadata: Option<EmitMetadata<P>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResponseStream {
|
||||||
|
event_id: u64,
|
||||||
|
ctx: Option<Context>,
|
||||||
|
receiver: Option<Receiver<Event>>,
|
||||||
|
timeout: Duration,
|
||||||
|
fut: Option<Pin<Box<dyn Future<Output = Result<(Option<Event>, Context, Receiver<Event>)>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseStream {
|
||||||
|
pub(crate) fn new(
|
||||||
|
event_id: u64,
|
||||||
|
timeout: Duration,
|
||||||
|
ctx: Context,
|
||||||
|
receiver: Receiver<Event>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
event_id,
|
||||||
|
ctx: Some(ctx),
|
||||||
|
receiver: Some(receiver),
|
||||||
|
timeout,
|
||||||
|
fut: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: IntoPayload> Unpin for EmitMetadataWithResponseStream<P> {}
|
||||||
|
|
||||||
|
impl<P: IntoPayload> EmitMetadataWithResponseStream<P> {
|
||||||
|
/// Sets a timeout for awaiting replies to this emitted event
|
||||||
|
#[inline]
|
||||||
|
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.timeout = Some(timeout);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: IntoPayload + Send + Sync + 'static> Future for EmitMetadataWithResponseStream<P> {
|
||||||
|
type Output = Result<ResponseStream>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
if self.fut.is_none() {
|
||||||
|
let mut emit_metadata = poll_unwrap!(self.emit_metadata.take());
|
||||||
|
let ctx = poll_unwrap!(emit_metadata
|
||||||
|
.event_metadata
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|m| m.ctx.clone()));
|
||||||
|
let timeout = self
|
||||||
|
.timeout
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(ctx.default_reply_timeout.clone());
|
||||||
|
|
||||||
|
let event_id = match poll_unwrap!(emit_metadata.event_metadata.as_mut()).get_event() {
|
||||||
|
Ok(e) => e.id(),
|
||||||
|
Err(e) => {
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.fut = Some(Box::pin(async move {
|
||||||
|
let tx = ctx.register_reply_listener(event_id).await?;
|
||||||
|
emit_metadata.await?;
|
||||||
|
|
||||||
|
Ok(ResponseStream::new(event_id, timeout, ctx, tx))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
self.fut.as_mut().unwrap().as_mut().poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unpin for ResponseStream {}
|
||||||
|
|
||||||
|
impl Stream for ResponseStream {
|
||||||
|
type Item = Result<Event>;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
if self.fut.is_none() {
|
||||||
|
if self.ctx.is_none() || self.receiver.is_none() {
|
||||||
|
return Poll::Ready(None);
|
||||||
|
}
|
||||||
|
let ctx = self.ctx.take().unwrap();
|
||||||
|
let mut receiver = self.receiver.take().unwrap();
|
||||||
|
let timeout = self.timeout;
|
||||||
|
let event_id = self.event_id;
|
||||||
|
|
||||||
|
self.fut = Some(Box::pin(async move {
|
||||||
|
let event: Option<Event> = tokio::select! {
|
||||||
|
tx_result = receiver.recv() => {
|
||||||
|
Ok(tx_result)
|
||||||
|
}
|
||||||
|
_ = tokio::time::sleep(timeout) => {
|
||||||
|
Err(Error::Timeout)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if event.is_none() || event.as_ref().unwrap().event_type() == EventType::End {
|
||||||
|
remove_reply_listener(&ctx, event_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((event, ctx, receiver))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.fut.as_mut().unwrap().as_mut().poll(cx) {
|
||||||
|
Poll::Ready(r) => match r {
|
||||||
|
Ok((event, ctx, tx)) => {
|
||||||
|
self.fut = None;
|
||||||
|
|
||||||
|
if let Some(event) = event {
|
||||||
|
if event.event_type() != EventType::End {
|
||||||
|
self.ctx = Some(ctx);
|
||||||
|
self.receiver = Some(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Some(Ok(event)))
|
||||||
|
} else {
|
||||||
|
Poll::Ready(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Poll::Ready(Some(Err(e))),
|
||||||
|
},
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
use crate::utils::call_counter::{get_counter_from_context, increment_counter_for_event};
|
||||||
|
use crate::utils::protocol::TestProtocolListener;
|
||||||
|
use crate::utils::{get_free_port, start_server_and_client};
|
||||||
|
use bromine::prelude::*;
|
||||||
|
use byteorder::ReadBytesExt;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
/// When awaiting the reply to an event the handler for the event doesn't get called.
|
||||||
|
/// Therefore we expect it to have a call count of 0.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn it_receives_responses() {
|
||||||
|
let port = get_free_port();
|
||||||
|
let ctx = get_client_with_server(port).await;
|
||||||
|
let mut reply_stream = ctx
|
||||||
|
.emit("stream", EmptyPayload)
|
||||||
|
.stream_replies()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut reply_stream_2 = ctx
|
||||||
|
.emit("stream", EmptyPayload)
|
||||||
|
.stream_replies()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for i in 0u8..=100 {
|
||||||
|
if let Some(Ok(event)) = reply_stream.next().await {
|
||||||
|
assert_eq!(event.payload::<NumberPayload>().unwrap().0, i)
|
||||||
|
} else {
|
||||||
|
panic!("stream 1 has no value {}", i);
|
||||||
|
}
|
||||||
|
if let Some(Ok(event)) = reply_stream_2.next().await {
|
||||||
|
assert_eq!(event.payload::<NumberPayload>().unwrap().0, i)
|
||||||
|
} else {
|
||||||
|
panic!("stream 2 has no value {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let counter = get_counter_from_context(&ctx).await;
|
||||||
|
assert_eq!(counter.get("stream").await, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_client_with_server(port: u8) -> Context {
|
||||||
|
start_server_and_client(move || get_builder(port)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_builder(port: u8) -> IPCBuilder<TestProtocolListener> {
|
||||||
|
IPCBuilder::new()
|
||||||
|
.address(port)
|
||||||
|
.timeout(Duration::from_millis(100))
|
||||||
|
.on("stream", callback!(handle_stream_event))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_stream_event(ctx: &Context, event: Event) -> IPCResult<Response> {
|
||||||
|
increment_counter_for_event(ctx, &event).await;
|
||||||
|
for i in 0u8..=99 {
|
||||||
|
ctx.emit("number", NumberPayload(i)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.response(NumberPayload(100))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EmptyPayload;
|
||||||
|
|
||||||
|
impl IntoPayload for EmptyPayload {
|
||||||
|
fn into_payload(self, _: &Context) -> IPCResult<Vec<u8>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NumberPayload(u8);
|
||||||
|
|
||||||
|
impl IntoPayload for NumberPayload {
|
||||||
|
fn into_payload(self, _: &Context) -> IPCResult<Vec<u8>> {
|
||||||
|
Ok(vec![self.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromPayload for NumberPayload {
|
||||||
|
fn from_payload<R: Read>(mut reader: R) -> IPCResult<Self> {
|
||||||
|
let num = reader.read_u8()?;
|
||||||
|
|
||||||
|
Ok(NumberPayload(num))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue