use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use axum::extract::{Path as AxumPath, Query, State};
use axum::http::{HeaderMap, StatusCode};
use axum::response::sse::{Event, Sse};
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::{Deserialize, Serialize};
use crate::AppState;
const REPLAY_BYTES: usize = 1 << 20;
const RETAIN_AFTER_FINISH: Duration = Duration::from_secs(120);
const MAX_SLOTS: usize = 64;
pub(crate) const RECONNECT_DELAY: Duration = Duration::from_millis(1500);
const POLL_WAIT: Duration = Duration::from_secs(10);
const RESUME_WAIT: Duration = Duration::from_secs(5);
const RESUME_KEEPALIVE_POLLS: u32 = 3;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct StoredEvent {
pub(crate) index: u64,
pub(crate) data: String,
}
#[derive(Default)]
struct SlotState {
events: VecDeque<StoredEvent>,
first_index: u64,
next_index: u64,
bytes: usize,
finished: bool,
finished_at: Option<Instant>,
}
pub(crate) struct StreamSlot {
request_id: String,
created_at: Instant,
state: Mutex<SlotState>,
notify: tokio::sync::Notify,
}
#[derive(Debug, PartialEq)]
pub(crate) struct ReadResult {
pub(crate) events: Vec<StoredEvent>,
pub(crate) next_index: u64,
pub(crate) finished: bool,
pub(crate) lost: bool,
}
impl StreamSlot {
fn new(request_id: &str) -> Self {
StreamSlot {
request_id: request_id.to_string(),
created_at: Instant::now(),
state: Mutex::new(SlotState::default()),
notify: tokio::sync::Notify::new(),
}
}
pub(crate) fn request_id(&self) -> &str {
&self.request_id
}
pub(crate) fn push(&self, data: String) -> u64 {
let index = {
let mut st = self.state.lock().unwrap_or_else(|p| p.into_inner());
let index = st.next_index;
st.next_index += 1;
st.bytes += data.len();
st.events.push_back(StoredEvent { index, data });
while st.bytes > REPLAY_BYTES && st.events.len() > 1 {
if let Some(dropped) = st.events.pop_front() {
st.bytes -= dropped.data.len();
st.first_index = dropped.index + 1;
}
}
index
};
self.notify.notify_waiters();
index
}
pub(crate) fn finish(&self) {
{
let mut st = self.state.lock().unwrap_or_else(|p| p.into_inner());
st.finished = true;
st.finished_at = Some(Instant::now());
}
self.notify.notify_waiters();
}
fn read_now(&self, cursor: u64) -> ReadResult {
let st = self.state.lock().unwrap_or_else(|p| p.into_inner());
if cursor < st.first_index {
return ReadResult {
events: Vec::new(),
next_index: st.first_index,
finished: st.finished,
lost: true,
};
}
let events: Vec<StoredEvent> = st
.events
.iter()
.filter(|e| e.index >= cursor)
.cloned()
.collect();
ReadResult {
next_index: events.last().map(|e| e.index + 1).unwrap_or(cursor),
events,
finished: st.finished,
lost: false,
}
}
pub(crate) async fn read_from(&self, cursor: u64, wait: Duration) -> ReadResult {
let deadline = Instant::now() + wait;
loop {
let notified = self.notify.notified();
let result = self.read_now(cursor);
if result.lost || result.finished || !result.events.is_empty() {
return result;
}
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return result;
}
if tokio::time::timeout(remaining, notified).await.is_err() {
return self.read_now(cursor);
}
}
}
fn droppable_at(&self, now: Instant) -> bool {
let st = self.state.lock().unwrap_or_else(|p| p.into_inner());
st.finished_at
.is_some_and(|at| now.duration_since(at) >= RETAIN_AFTER_FINISH)
}
}
#[derive(Default)]
pub(crate) struct StreamRegistry {
slots: Mutex<HashMap<String, Arc<StreamSlot>>>,
}
impl StreamRegistry {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn register(&self, request_id: &str) -> Arc<StreamSlot> {
let slot = Arc::new(StreamSlot::new(request_id));
let mut slots = self.slots.lock().unwrap_or_else(|p| p.into_inner());
let now = Instant::now();
slots.retain(|_, s| !s.droppable_at(now));
while slots.len() >= MAX_SLOTS {
let victim = slots
.values()
.filter(|s| s.state.lock().unwrap_or_else(|p| p.into_inner()).finished)
.min_by_key(|s| s.created_at)
.map(|s| s.request_id.clone());
match victim {
Some(id) => {
slots.remove(&id);
}
None => break,
}
}
slots.insert(request_id.to_string(), Arc::clone(&slot));
slot
}
pub(crate) fn get(&self, request_id: &str) -> Option<Arc<StreamSlot>> {
self.slots
.lock()
.unwrap_or_else(|p| p.into_inner())
.get(request_id)
.cloned()
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.slots.lock().unwrap_or_else(|p| p.into_inner()).len()
}
}
pub(crate) struct Emitter {
slot: Option<Arc<StreamSlot>>,
}
impl Emitter {
pub(crate) fn new(slot: Option<Arc<StreamSlot>>) -> Self {
Emitter { slot }
}
pub(crate) fn is_resumable(&self) -> bool {
self.slot.is_some()
}
fn emit(&self, data: String) -> Event {
match &self.slot {
None => sse_event(data, None, false),
Some(slot) => {
let index = slot.push(data.clone());
sse_event(data, Some(event_id(slot.request_id(), index)), index == 0)
}
}
}
pub(crate) fn event<T: Serialize>(&self, payload: &T) -> Event {
let data = serde_json::to_string(payload).unwrap_or_else(|e| {
tracing::error!("failed to serialize a stream chunk: {e}");
"{}".to_string()
});
self.emit(data)
}
pub(crate) fn done(&self) -> Event {
self.emit("[DONE]".to_string())
}
fn finish(&self) {
if let Some(slot) = &self.slot {
slot.finish();
}
}
}
impl Drop for Emitter {
fn drop(&mut self) {
self.finish();
}
}
pub(crate) fn sse_event(data: String, id: Option<String>, first: bool) -> Event {
let mut event = Event::default();
if let Some(id) = id {
event = event.id(id);
if first {
event = event.retry(RECONNECT_DELAY);
}
}
event.data(data)
}
pub(crate) fn event_id(request_id: &str, index: u64) -> String {
format!("{request_id}:{index}")
}
pub(crate) fn cursor_from_event_id(request_id: &str, last_event_id: &str) -> Option<u64> {
let (id, index) = last_event_id.rsplit_once(':')?;
if id != request_id {
return None;
}
index.parse::<u64>().ok().map(|i| i + 1)
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResumeQuery {
#[serde(default)]
last_event_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct PollQuery {
#[serde(default)]
from: Option<u64>,
}
#[derive(Debug, Serialize)]
pub(crate) struct PollResponse {
pub(crate) request_id: String,
pub(crate) events: Vec<StoredEvent>,
pub(crate) next_index: u64,
pub(crate) done: bool,
}
fn unknown_stream(request_id: &str) -> Response {
(
StatusCode::NOT_FOUND,
Json(serde_json::json!({
"error": {
"message": format!(
"no resumable stream with request_id '{request_id}'. It was never \
started with stream_resumable, or it finished long enough ago to \
have been forgotten."
),
"type": "invalid_request_error",
"code": "stream_not_found",
}
})),
)
.into_response()
}
fn replay_window_lost(request_id: &str, first_index: u64) -> Response {
(
StatusCode::GONE,
Json(serde_json::json!({
"error": {
"message": format!(
"the replay window for '{request_id}' has moved past the requested \
position; the earliest event still held is {first_index}. Resuming \
from here would skip part of the answer without either end being \
able to tell."
),
"type": "invalid_request_error",
"code": "replay_window_lost",
}
})),
)
.into_response()
}
pub(crate) async fn resume(
State(state): State<Arc<AppState>>,
AxumPath(request_id): AxumPath<String>,
headers: HeaderMap,
Query(query): Query<ResumeQuery>,
) -> Response {
let Some(slot) = state.streams.get(&request_id) else {
return unknown_stream(&request_id);
};
let last_event_id = headers
.get("last-event-id")
.and_then(|v| v.to_str().ok())
.map(str::to_string)
.or(query.last_event_id);
let cursor = match last_event_id {
None => 0,
Some(id) => match cursor_from_event_id(&request_id, &id) {
Some(cursor) => cursor,
None => {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": {
"message": format!(
"Last-Event-ID '{id}' does not name a position in \
'{request_id}'"
),
"type": "invalid_request_error",
"code": "bad_last_event_id",
}
})),
)
.into_response();
}
},
};
let first = slot.read_now(cursor);
if first.lost {
return replay_window_lost(&request_id, first.next_index);
}
let keepalive_id = request_id.clone();
let stream = futures_util::stream::unfold(
(slot, cursor, true, 0u32),
move |(slot, cursor, is_first, quiet)| {
let keepalive_id = keepalive_id.clone();
async move {
let result = slot.read_from(cursor, RESUME_WAIT).await;
if result.lost {
return None;
}
if result.events.is_empty() {
if result.finished {
return None;
}
let quiet = quiet + 1;
let events = if quiet % RESUME_KEEPALIVE_POLLS == 0 {
vec![Ok(crate::sse::keepalive_event(&serde_json::json!({
"id": keepalive_id,
"object": "chat.completion.chunk",
"choices": [],
})))]
} else {
Vec::new()
};
return Some((events, (slot, cursor, is_first, quiet)));
}
let next = result.next_index;
let request_id = slot.request_id().to_string();
let events: Vec<Result<Event, std::convert::Infallible>> = result
.events
.into_iter()
.enumerate()
.map(|(i, e)| {
Ok(sse_event(
e.data,
Some(event_id(&request_id, e.index)),
is_first && i == 0,
))
})
.collect();
Some((events, (slot, next, false, 0)))
}
},
);
(
[(
axum::http::HeaderName::from_static("x-accel-buffering"),
axum::http::HeaderValue::from_static("no"),
)],
Sse::new(futures_util::StreamExt::flat_map(
stream,
futures_util::stream::iter,
)),
)
.into_response()
}
pub(crate) async fn poll(
State(state): State<Arc<AppState>>,
AxumPath(request_id): AxumPath<String>,
Query(query): Query<PollQuery>,
) -> Response {
let Some(slot) = state.streams.get(&request_id) else {
return unknown_stream(&request_id);
};
let cursor = query.from.unwrap_or(0);
let result = slot.read_from(cursor, POLL_WAIT).await;
if result.lost {
return replay_window_lost(&request_id, result.next_index);
}
Json(PollResponse {
request_id,
done: result.finished && result.events.is_empty(),
next_index: result.next_index,
events: result.events,
})
.into_response()
}
#[cfg(test)]
mod tests {
use super::*;
fn slot() -> StreamSlot {
StreamSlot::new("chatcmpl-1")
}
#[tokio::test]
async fn a_reader_from_zero_gets_everything_in_order() {
let slot = slot();
for i in 0..3 {
assert_eq!(slot.push(format!("event-{i}")), i);
}
slot.finish();
let result = slot.read_from(0, Duration::ZERO).await;
assert_eq!(
result
.events
.iter()
.map(|e| e.data.as_str())
.collect::<Vec<_>>(),
vec!["event-0", "event-1", "event-2"]
);
assert_eq!(result.next_index, 3);
assert!(result.finished);
assert!(!result.lost);
}
#[tokio::test]
async fn a_resume_gets_only_what_came_after_the_last_seen_id() {
let slot = slot();
for i in 0..5 {
slot.push(format!("event-{i}"));
}
let cursor = cursor_from_event_id("chatcmpl-1", "chatcmpl-1:2").unwrap();
assert_eq!(cursor, 3);
let result = slot.read_from(cursor, Duration::ZERO).await;
assert_eq!(
result
.events
.iter()
.map(|e| e.data.as_str())
.collect::<Vec<_>>(),
vec!["event-3", "event-4"]
);
}
#[test]
fn an_event_id_from_another_stream_is_refused_not_rounded_down() {
assert_eq!(cursor_from_event_id("chatcmpl-1", "chatcmpl-1:0"), Some(1));
assert_eq!(cursor_from_event_id("chatcmpl-1", "chatcmpl-2:9"), None);
assert_eq!(cursor_from_event_id("chatcmpl-1", "7"), None);
assert_eq!(cursor_from_event_id("chatcmpl-1", "chatcmpl-1:x"), None);
assert_eq!(event_id("chatcmpl-1", 4), "chatcmpl-1:4");
}
#[tokio::test]
async fn a_position_that_has_been_evicted_is_reported_lost() {
let slot = slot();
let big = "x".repeat(REPLAY_BYTES / 4);
for _ in 0..8 {
slot.push(big.clone());
}
let result = slot.read_from(0, Duration::ZERO).await;
assert!(result.lost, "the window has moved past index 0");
assert!(result.events.is_empty());
assert!(
result.next_index > 0,
"the caller is told where it can start"
);
let inside = slot.read_from(result.next_index, Duration::ZERO).await;
assert!(!inside.lost);
assert!(!inside.events.is_empty());
}
#[tokio::test]
async fn the_window_never_evicts_the_only_event_it_holds() {
let slot = slot();
slot.push("y".repeat(REPLAY_BYTES * 2));
let result = slot.read_from(0, Duration::ZERO).await;
assert!(!result.lost);
assert_eq!(result.events.len(), 1);
}
#[tokio::test]
async fn a_waiting_reader_is_woken_by_the_next_event() {
let slot = Arc::new(slot());
let writer = Arc::clone(&slot);
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(20)).await;
writer.push("late".to_string());
});
let started = Instant::now();
let result = slot.read_from(0, Duration::from_secs(5)).await;
assert_eq!(result.events.len(), 1);
assert!(
started.elapsed() < Duration::from_secs(2),
"the reader waited for the timeout instead of the event"
);
}
#[tokio::test]
async fn a_finished_stream_stops_its_readers_immediately() {
let slot = slot();
slot.push("only".to_string());
slot.finish();
let drained = slot.read_from(1, Duration::from_secs(30)).await;
assert!(drained.events.is_empty());
assert!(drained.finished);
}
#[test]
fn a_non_resumable_emitter_buffers_nothing_and_stops_on_a_lost_receiver() {
let emitter = Emitter::new(None);
assert!(!emitter.is_resumable());
emitter.finish();
}
#[test]
fn a_resumable_emitter_numbers_its_events_from_zero_and_buffers_them() {
let registry = StreamRegistry::new();
let slot = registry.register("chatcmpl-e");
let emitter = Emitter::new(Some(Arc::clone(&slot)));
assert!(emitter.is_resumable());
let _ = emitter.event(&serde_json::json!({"n": 1}));
let _ = emitter.done();
emitter.finish();
let result = slot.read_now(0);
assert_eq!(result.events.len(), 2);
assert_eq!(result.events[0].index, 0);
assert_eq!(result.events[0].data, r#"{"n":1}"#);
assert_eq!(
result.events[1].data, "[DONE]",
"the end of stream is replayable too, or a resumed reader never stops"
);
assert!(result.finished);
}
#[test]
fn dropping_an_emitter_closes_its_buffer_even_when_nothing_finished_it() {
let registry = StreamRegistry::new();
let slot = registry.register("chatcmpl-panic");
{
let emitter = Emitter::new(Some(Arc::clone(&slot)));
let _ = emitter.event(&serde_json::json!({"n": 1}));
}
assert!(
slot.read_now(0).finished,
"a dropped emitter must close its buffer"
);
}
#[test]
fn a_registered_stream_is_findable_by_its_request_id() {
let registry = StreamRegistry::new();
let slot = registry.register("chatcmpl-a");
slot.push("hi".to_string());
assert!(registry.get("chatcmpl-a").is_some());
assert!(registry.get("chatcmpl-b").is_none());
}
#[test]
fn the_registry_evicts_finished_streams_before_live_ones() {
let registry = StreamRegistry::new();
for i in 0..MAX_SLOTS {
let slot = registry.register(&format!("chatcmpl-{i}"));
if i > 0 {
slot.finish();
}
}
assert_eq!(registry.len(), MAX_SLOTS);
registry.register("chatcmpl-new");
assert!(registry.len() <= MAX_SLOTS);
assert!(
registry.get("chatcmpl-0").is_some(),
"a live stream was evicted out from under its reader"
);
assert!(registry.get("chatcmpl-new").is_some());
}
}