public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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




             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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal