#[cfg(target_arch = "wasm32")]
use std::future::Future;
use std::time::Duration;
#[cfg(target_arch = "wasm32")]
use bytes::Bytes;
#[cfg(target_arch = "wasm32")]
use futures_core::Stream;
#[cfg(target_arch = "wasm32")]
use crate::error::LiterLlmError;
#[cfg(target_arch = "wasm32")]
const MAX_TIMER: Duration = Duration::from_millis(i32::MAX as u64);
#[derive(Clone, Copy)]
pub(crate) struct Deadline {
#[cfg(target_arch = "wasm32")]
end: Option<web_time::Instant>,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(
not(target_arch = "wasm32"),
allow(dead_code, reason = "only the wasm32 build raises it")
)]
pub(crate) struct Elapsed;
impl Deadline {
#[cfg_attr(
not(target_arch = "wasm32"),
allow(unused_variables, reason = "native clients enforce the limit")
)]
pub(crate) fn after(limit: Option<Duration>) -> Self {
Self {
#[cfg(target_arch = "wasm32")]
end: limit
.filter(|limit| *limit <= MAX_TIMER)
.map(|limit| web_time::Instant::now() + limit),
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) async fn race<F: Future>(self, fut: F) -> Result<F::Output, Elapsed> {
use std::pin::pin;
use std::task::Poll;
let Some(end) = self.end else {
return Ok(fut.await);
};
let mut fut = pin!(fut);
let mut timer = pin!(gloo_timers::future::sleep(
end.saturating_duration_since(web_time::Instant::now())
));
std::future::poll_fn(|cx| {
if let Poll::Ready(output) = fut.as_mut().poll(cx) {
return Poll::Ready(Ok(output));
}
if timer.as_mut().poll(cx).is_ready() {
return Poll::Ready(Err(Elapsed));
}
Poll::Pending
})
.await
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::unused_async, reason = "same signature as the wasm32 build")]
pub(crate) async fn race<F: std::future::Future>(self, fut: F) -> Result<F::Output, Elapsed> {
Ok(fut.await)
}
pub(crate) async fn run<T, F>(self, fut: F) -> crate::error::Result<T>
where
F: std::future::Future<Output = crate::error::Result<T>>,
{
match self.race(fut).await {
Ok(result) => result,
Err(Elapsed) => Err(crate::error::LiterLlmError::Timeout),
}
}
}
#[cfg(target_arch = "wasm32")]
pin_project_lite::pin_project! {
pub(crate) struct IdleTimeout<S> {
#[pin]
inner: S,
limit: Option<Duration>,
timer: Option<std::pin::Pin<Box<gloo_timers::future::TimeoutFuture>>>,
finished: bool,
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn idle_timeout<S>(stream: S, limit: Option<Duration>) -> IdleTimeout<S> {
IdleTimeout {
inner: stream,
limit: limit.filter(|limit| *limit <= MAX_TIMER),
timer: None,
finished: false,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn idle_timeout<S>(stream: S, _limit: Option<Duration>) -> S {
stream
}
#[cfg(target_arch = "wasm32")]
impl<S, E> Stream for IdleTimeout<S>
where
S: Stream<Item = Result<Bytes, E>>,
LiterLlmError: From<E>,
{
type Item = Result<Bytes, LiterLlmError>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
use std::future::Future;
use std::task::Poll;
let mut this = self.project();
if *this.finished {
return Poll::Ready(None);
}
match this.inner.as_mut().poll_next(cx) {
Poll::Ready(item) => {
*this.timer = None;
Poll::Ready(item.map(|chunk| chunk.map_err(LiterLlmError::from)))
}
Poll::Pending => {
let Some(limit) = *this.limit else {
return Poll::Pending;
};
let timer = this
.timer
.get_or_insert_with(|| Box::pin(gloo_timers::future::sleep(limit)));
if timer.as_mut().poll(cx).is_ready() {
*this.timer = None;
*this.finished = true;
return Poll::Ready(Some(Err(LiterLlmError::Timeout)));
}
Poll::Pending
}
}
}
}