all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Kefu Chai <k.chai@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH v5 http-server 2/2] apiserver: flush queued data before closing a proxied connection
Date: Fri, 11 Sep 2026 22:18:46 +0800	[thread overview]
Message-ID: <20260911141846.9888-3-k.chai@proxmox.com> (raw)
In-Reply-To: <20260911141846.9888-1-k.chai@proxmox.com>

When one side of a proxied WebSocket or SPICE connection closes, the
proxy disconnects right away and drops the data still queued for the
other side. client_do_disconnect() shuts the socket down before its
wbuf is written, and the client handle has linger set to 0. With the
backpressure limit this is up to 640 KB at the end of a transfer.

On EOF from either side, stop reading from the other side and
disconnect from its on_drain callback once the queued data is written.
Also clear the on_drain callback of the closed side, so a paused reader
of the other side is not resumed. When waiting for the client, the 60
second write timeout applies, so a client that stops reading does not
keep the connection open. When waiting for the backend, there is no
timeout, for the same reason as in apply_read_backpressure().

Signed-off-by: Kefu Chai <k.chai@proxmox.com>
---
 src/PVE/APIServer/AnyEvent.pm | 41 +++++++++++++++++++++++++++++++++--
 1 file changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm
index 826e1ff..c8266df 100644
--- a/src/PVE/APIServer/AnyEvent.pm
+++ b/src/PVE/APIServer/AnyEvent.pm
@@ -259,6 +259,31 @@ sub abort_request {
     if (my $err = $@) { syslog('err', $err); }
 }
 
+# on_eof body for proxied connections: stop forwarding, and disconnect once
+# $peer_hdl has written out what is still queued for it.
+sub disconnect_after_drain {
+    my ($self, $reqstate, $hdl, $peer_hdl, $timeout) = @_;
+
+    if (!$peer_hdl) {
+        $self->abort_request($reqstate);
+        return;
+    }
+
+    # drop a paused $peer_hdl reader, so it is not resumed
+    $hdl->on_drain(undef);
+    $hdl->wtimeout(0);
+    $peer_hdl->on_read();
+    $peer_hdl->stop_read();
+
+    # only pass $timeout if $peer_hdl is the client, see apply_read_backpressure
+    if ($timeout) {
+        $peer_hdl->wtimeout_reset();
+        $peer_hdl->wtimeout($timeout);
+    }
+    # invoked right away if nothing is queued
+    $peer_hdl->on_drain(sub { $self->abort_request($reqstate) });
+}
+
 sub response_stream {
     my ($self, $reqstate, $stream_fh) = @_;
 
@@ -620,7 +645,9 @@ sub websocket_proxy {
                 timeout => 5,
                 on_eof => sub {
                     my ($hdl) = @_;
-                    $self->abort_request($reqstate);
+                    $self->disconnect_after_drain(
+                        $reqstate, $hdl, $reqstate->{hdl}, $backpressure_stall_timeout,
+                    );
                 },
                 on_error => sub {
                     my ($hdl, $fatal, $message) = @_;
@@ -728,6 +755,10 @@ sub websocket_proxy {
             $reqstate->{proxyhdl}->timeout(0);
             $reqstate->{proxyhdl}->on_read($proxyhdlreader);
             $reqstate->{hdl}->on_read($hdlreader);
+            $reqstate->{hdl}->on_eof(sub {
+                my ($hdl) = @_;
+                $self->disconnect_after_drain($reqstate, $hdl, $reqstate->{proxyhdl});
+            });
 
             # FIXME: remove protocol in PVE/PMG 8.x
             #
@@ -1141,7 +1172,9 @@ sub handle_spice_proxy_request {
                 timeout => 5,
                 on_eof => sub {
                     my ($hdl) = @_;
-                    $self->abort_request($reqstate);
+                    $self->disconnect_after_drain(
+                        $reqstate, $hdl, $reqstate->{hdl}, $backpressure_stall_timeout,
+                    );
                 },
                 on_error => sub {
                     my ($hdl, $fatal, $message) = @_;
@@ -1179,6 +1212,10 @@ sub handle_spice_proxy_request {
                 $reqstate->{proxyhdl}->timeout(0);
                 $reqstate->{proxyhdl}->on_read($proxyhdlreader);
                 $reqstate->{hdl}->on_read($hdlreader);
+                $reqstate->{hdl}->on_eof(sub {
+                    my ($hdl) = @_;
+                    $self->disconnect_after_drain($reqstate, $hdl, $reqstate->{proxyhdl});
+                });
 
                 # a response must be followed by an empty line
                 my $res = "$proto 200 OK\015\012\015\012";
-- 
2.47.3





      parent reply	other threads:[~2026-09-11 14:19 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-11 14:18 [PATCH v5 http-server 0/2] fix pveproxy OOM in websocket and spice proxy handlers Kefu Chai
2026-09-11 14:18 ` [PATCH v5 http-server 1/2] fix #7483: apiserver: add backpressure to " Kefu Chai
2026-09-11 14:18 ` Kefu Chai [this message]

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=20260911141846.9888-3-k.chai@proxmox.com \
    --to=k.chai@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal