From: Matteo Nastasi Date: Sun, 13 Sep 2026 10:37:55 +0000 (+0200) Subject: the comet stream was closed without the final chunk X-Git-Url: https://mop.ddnsfree.com/gitweb/?a=commitdiff_plain;h=8994f0b1ed10fb00f410d6b3f1ec58380d8b4a29;p=brisk.git the comet stream was closed without the final chunk Streaming responses declare Transfer-Encoding: chunked and the data goes out framed by chunked_content(), but on close the zero length chunk that terminates the response was never sent. With apache it went unnoticed: the daemon owned the client socket and simply closed it. nginx instead parses the response, and every stream that renews itself (RD_ENDTIME_DELTA, 240 seconds) looked truncated to it: "upstream prematurely closed connection while reading upstream", one error line per connected player per cycle. The final chunk is added in User::stream_close(), which is already the place where the daemon writes its last bytes before closing, and only for the transports that really are chunked: websockets are not, and keep sending their own close frame. Measured: with 150 players the renewal of the streams produced 150 errors; after the fix, 30 players and 30 stream reopenings give zero errors. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014M1jiEq9cHdE5SE5j6vFuE --- diff --git a/web/Obj/user.phh b/web/Obj/user.phh index c98de14..495e61a 100644 --- a/web/Obj/user.phh +++ b/web/Obj/user.phh @@ -880,7 +880,23 @@ function stream_keepalive($with_ping) function stream_close() { - return ($this->rd_transp->close()); + $clo = $this->rd_transp->close(); + + /* Streaming responses declare Transfer-Encoding: chunked (see + headers_render) and the data goes out framed by chunked_content(), but + the final chunk was missing on close: whoever sits in the middle sees + the connection drop halfway through the response. With apache it went + unnoticed, because the daemon owned the client socket; nginx instead + parses the response and logs "upstream prematurely closed connection" + for every stream that renews itself. */ + if ($this->is_chunked()) { + if ($clo != "") { + $clo = sprintf("%X\r\n", mb_strlen($clo, "ASCII")).$clo."\r\n"; + } + $clo .= "0\r\n\r\n"; + } + + return ($clo); } function stream_postclose_get($sock, $curtime)