fix: include http_request_id in request-wise priming event IDs (#799)
* fix: include http_request_id in request-wise priming event IDs * refactor: use Option::into_iter and usize::from for priming * fix: retain event cache for completed request-wise channels * fix: track completed_at for cache eviction and resume * fix: log resume failures at warn level * test: add completed_cache_ttl eviction test * fix: return empty stream on failed resume * test: add resume after completion test
This commit is contained in:
parent
6603c1ff15
commit
3e56d52764
1 changed files with 467 additions and 89 deletions
|
|
@ -478,40 +478,52 @@ where
|
|||
.and_then(|v| v.to_str().ok())
|
||||
.map(|s| s.to_owned());
|
||||
if let Some(last_event_id) = last_event_id {
|
||||
// check if session has this event id
|
||||
let stream = self
|
||||
match self
|
||||
.session_manager
|
||||
.resume(&session_id, last_event_id)
|
||||
.await
|
||||
.map_err(internal_error_response("resume session"))?;
|
||||
// Resume doesn't need priming - client already has the event ID
|
||||
Ok(sse_stream_response(
|
||||
stream,
|
||||
self.config.sse_keep_alive,
|
||||
self.config.cancellation_token.child_token(),
|
||||
))
|
||||
} else {
|
||||
// create standalone stream
|
||||
let stream = self
|
||||
.session_manager
|
||||
.create_standalone_stream(&session_id)
|
||||
.await
|
||||
.map_err(internal_error_response("create standalone stream"))?;
|
||||
// Prepend priming event if sse_retry configured
|
||||
let stream = if let Some(retry) = self.config.sse_retry {
|
||||
let priming = ServerSseMessage::priming("0", retry);
|
||||
futures::stream::once(async move { priming })
|
||||
.chain(stream)
|
||||
.left_stream()
|
||||
} else {
|
||||
stream.right_stream()
|
||||
};
|
||||
Ok(sse_stream_response(
|
||||
stream,
|
||||
self.config.sse_keep_alive,
|
||||
self.config.cancellation_token.child_token(),
|
||||
))
|
||||
{
|
||||
Ok(stream) => {
|
||||
return Ok(sse_stream_response(
|
||||
stream,
|
||||
self.config.sse_keep_alive,
|
||||
self.config.cancellation_token.child_token(),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
// Return 200 with an immediately-closed empty stream.
|
||||
// Returning an HTTP error would cause EventSource to retry
|
||||
// with the same Last-Event-ID in an infinite loop. An empty
|
||||
// 200 cleanly terminates the EventSource without delivering
|
||||
// events from a different stream.
|
||||
tracing::warn!("Resume failed ({e}), returning empty stream");
|
||||
return Ok(sse_stream_response(
|
||||
futures::stream::empty(),
|
||||
None,
|
||||
self.config.cancellation_token.child_token(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// No Last-Event-ID — create standalone stream
|
||||
let stream = self
|
||||
.session_manager
|
||||
.create_standalone_stream(&session_id)
|
||||
.await
|
||||
.map_err(internal_error_response("create standalone stream"))?;
|
||||
let stream = if let Some(retry) = self.config.sse_retry {
|
||||
let priming = ServerSseMessage::priming("0", retry);
|
||||
futures::stream::once(async move { priming })
|
||||
.chain(stream)
|
||||
.left_stream()
|
||||
} else {
|
||||
stream.right_stream()
|
||||
};
|
||||
Ok(sse_stream_response(
|
||||
stream,
|
||||
self.config.sse_keep_alive,
|
||||
self.config.cancellation_token.child_token(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle_post<B>(&self, request: Request<B>) -> Result<BoxResponse, BoxResponse>
|
||||
|
|
@ -598,20 +610,14 @@ where
|
|||
|
||||
match message {
|
||||
ClientJsonRpcMessage::Request(_) => {
|
||||
// Priming for request-wise streams is handled by the
|
||||
// session layer (SessionManager::create_stream) which
|
||||
// has access to the http_request_id for correct event IDs.
|
||||
let stream = self
|
||||
.session_manager
|
||||
.create_stream(&session_id, message)
|
||||
.await
|
||||
.map_err(internal_error_response("get session"))?;
|
||||
// Prepend priming event if sse_retry configured
|
||||
let stream = if let Some(retry) = self.config.sse_retry {
|
||||
let priming = ServerSseMessage::priming("0", retry);
|
||||
futures::stream::once(async move { priming })
|
||||
.chain(stream)
|
||||
.left_stream()
|
||||
} else {
|
||||
stream.right_stream()
|
||||
};
|
||||
Ok(sse_stream_response(
|
||||
stream,
|
||||
self.config.sse_keep_alive,
|
||||
|
|
|
|||
Loading…
Reference in a new issue