From: Dietmar Maurer <dietmar@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH rest-server] rest-server: stop a json-seq stream once its client is gone
Date: Sat, 17 Oct 2026 18:16:56 +0200 [thread overview]
Message-ID: <20261017161656.3529676-1-dietmar@proxmox.com> (raw)
The task forwarding the records of a streamed response only noticed
a closed connection when sending the next record failed. A stream
that stays silent for a long time, such as an API handler waiting on
a slow backend, thus kept running after its client went away, and so
did the work behind it, as the producer only learns of the gone
client once the stream is dropped.
Stop forwarding as soon as the client is gone, which drops the stream
right away.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
proxmox-rest-server/src/rest.rs | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/proxmox-rest-server/src/rest.rs b/proxmox-rest-server/src/rest.rs
index 779851eb..f47761bd 100644
--- a/proxmox-rest-server/src/rest.rs
+++ b/proxmox-rest-server/src/rest.rs
@@ -896,7 +896,16 @@ fn handle_stream_as_json_seq(stream: proxmox_router::Stream) -> Result<Response<
use futures::StreamExt;
let mut stream = stream.into_inner();
- while let Some(record) = stream.next().await {
+ loop {
+ // A stream may stay silent for a long time. Stop once the client is gone, which drops
+ // the stream, so its producer notices without having to send another record.
+ let record = tokio::select! {
+ record = stream.next() => record,
+ () = send.closed() => break,
+ };
+ let Some(record) = record else {
+ break;
+ };
if send.send(Ok(record.to_bytes())).await.is_err() {
break;
}
@@ -1475,6 +1484,21 @@ impl Unformatted {
mod tests {
use super::*;
+ #[tokio::test]
+ async fn json_seq_stream_stops_once_the_client_is_gone() {
+ let (send, receive) = tokio::sync::mpsc::unbounded_channel::<proxmox_router::Record>();
+ let stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receive);
+ let response = handle_stream_as_json_seq(proxmox_router::Stream::from(stream)).unwrap();
+ tokio::task::yield_now().await;
+ assert!(!send.is_closed());
+
+ // the stream sends nothing, yet it is dropped with the response
+ drop(response);
+ tokio::time::timeout(std::time::Duration::from_secs(5), send.closed())
+ .await
+ .expect("stream of a gone client still running");
+ }
+
fn headers(name: &'static str, value: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(name, value.parse().unwrap());
--
2.47.3
next reply other threads:[~2026-10-17 16:17 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 16:16 Dietmar Maurer [this message]
2026-10-17 20:29 ` applied: [PATCH rest-server] rest-server: stop a json-seq stream once its client is gone Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261017161656.3529676-1-dietmar@proxmox.com \
--to=dietmar@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox