use std::time::Duration;
use tokio_util::sync::CancellationToken;
use super::contract::{ChatChunk, ChatStream, FinishReason};
use super::error::EngineError;
pub const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
pub fn engine_client() -> reqwest::Client {
reqwest::Client::builder()
.connect_timeout(CONNECT_TIMEOUT)
.build()
.unwrap_or_else(|_| reqwest::Client::new())
}
pub async fn send_cancellable(
rb: reqwest::RequestBuilder,
cancel: &CancellationToken,
) -> Result<Option<reqwest::Response>, EngineError> {
tokio::select! {
biased;
_ = cancel.cancelled() => Ok(None),
sent = rb.send() => match sent {
Ok(response) => Ok(Some(response)),
Err(err) => Err(EngineError::transport(&err)),
},
}
}
pub fn cancelled_stream() -> ChatStream {
Box::pin(futures_util::stream::once(async {
ChatChunk::Finished(FinishReason::Cancelled)
}))
}
#[cfg(test)]
mod tests {
use futures_util::StreamExt;
use super::*;
#[tokio::test]
async fn cancelled_stream_yields_one_cancelled_finish() {
let mut s = cancelled_stream();
assert_eq!(
s.next().await,
Some(ChatChunk::Finished(FinishReason::Cancelled))
);
assert_eq!(s.next().await, None);
}
fn dead_url() -> String {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = l.local_addr().unwrap().port();
drop(l);
format!("http://127.0.0.1:{port}/v1/chat/completions")
}
#[tokio::test]
async fn a_cancelled_token_short_circuits_the_send() {
let cancel = CancellationToken::new();
cancel.cancel();
let client = engine_client();
let result = send_cancellable(client.get(dead_url()), &cancel).await;
assert!(matches!(result, Ok(None)));
}
#[tokio::test]
async fn a_refused_connection_is_a_transient_error_with_a_reason() {
let client = engine_client();
let cancel = CancellationToken::new();
let err = send_cancellable(client.get(dead_url()), &cancel)
.await
.expect_err("nothing is listening on that port");
assert!(err.is_transient(), "a transport failure is worth a retry");
assert_eq!(err.status, None);
assert!(
err.message.len() > "error sending request".len(),
"the cause must reach the message, not only the log: {}",
err.message
);
}
}