[projects/buildlogs] Reap the decompressor and detect aborted clients in dump_log/dump_text.
arekm
arekm at pld-linux.org
Mon May 4 18:56:57 CEST 2026
commit c54450c17c6fa4b5584fbbb51f589e7809508a04
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Mon May 4 18:56:11 2026 +0200
Reap the decompressor and detect aborted clients in dump_log/dump_text.
Long-running requests were leaving lbzcat children chewing CPU and
piling up as zombies when the client disconnected or PHP died unhappily.
Switch to single-threaded bzcat, terminate the proc on close, register
a shutdown hook so cleanup runs on any exit path, and add a periodic
heartbeat in the read loop so connection_aborted() can trip mid-stream.
index.php | 36 ++++++++++++++++++++++++++++++++++++
lib.php | 14 +++++++++-----
2 files changed, 45 insertions(+), 5 deletions(-)
---
diff --git a/index.php b/index.php
index 964f1ae..749b6a8 100644
--- a/index.php
+++ b/index.php
@@ -801,6 +801,8 @@ function dump_log($tail)
global $root_directory, $big_url, $ns, $id, $cnt, $off;
global $buildlogs_server;
+ ignore_user_abort(false);
+
$f = file_name();
if ($f == false)
@@ -893,6 +895,17 @@ function dump_log($tail)
echo "</table>";
return;
}
+ // Make sure the decompressor child gets reaped (and killed) even if PHP
+ // dies on a fatal, max_execution_time, or unnoticed client abort. Without
+ // this, lbzcat keeps burning CPU and accumulates as a zombie. Guarded with
+ // is_resource() because close_log_stream() may have already run cleanly,
+ // and proc_terminate/proc_close on a freed resource throws TypeError in
+ // PHP 8 (which @ does not suppress).
+ register_shutdown_function(function() use ($h) {
+ if (is_resource($h['proc'])) { @proc_terminate($h['proc'], 15); }
+ if (is_resource($h['fd'])) { @fclose($h['fd']); }
+ if (is_resource($h['proc'])) { @proc_close($h['proc']); }
+ });
$fd = $h['fd'];
$toc = array();
$err = array();
@@ -903,7 +916,21 @@ function dump_log($tail)
$out_buf_size = 0;
$err_count = 0;
$seen_sections = array();
+ $tick = 0;
while (($s = fgets($fd, 102400)) != false) {
+ // Periodically poke the socket so PHP notices if the client is gone.
+ // Best-effort: with mod_deflate or PHP buffering this may not actually
+ // reach the wire mid-loop, but the shutdown hook is the real safety
+ // net — this just lets us bail early when it does work.
+ if ((++$tick & 0x7FF) === 0) {
+ echo "<!-- . -->";
+ @ob_flush();
+ @flush();
+ if (connection_aborted()) {
+ close_log_stream($h);
+ return;
+ }
+ }
$toc_elem = false;
$err_elem = false;
@@ -1058,6 +1085,15 @@ function dump_text()
echo "# failed to spawn decompressor\n";
return;
}
+ // Reap the decompressor on PHP fatal / timeout / late-noticed abort so we
+ // don't accumulate lbzcat zombies still chewing on the file. See dump_log
+ // for the is_resource() rationale (PHP 8 TypeError on freed resource).
+ register_shutdown_function(function() use ($h) {
+ if (is_resource($h['proc'])) { @proc_terminate($h['proc'], 15); }
+ if (is_resource($h['fd'])) { @fclose($h['fd']); }
+ if (is_resource($h['proc'])) { @proc_close($h['proc']); }
+ });
+ ignore_user_abort(false);
fpassthru($h['fd']);
close_log_stream($h);
}
diff --git a/lib.php b/lib.php
index b67f217..6fd3edc 100644
--- a/lib.php
+++ b/lib.php
@@ -9,9 +9,7 @@ function open_db(string $dsn): PDO {
}
function decompressor_for(string $path): string {
- if (str_ends_with($path, '.bz2')) {
- return is_executable('/usr/bin/lbzcat') ? '/usr/bin/lbzcat' : '/usr/bin/bzcat';
- }
+ if (str_ends_with($path, '.bz2')) return '/usr/bin/bzcat';
if (str_ends_with($path, '.gz')) return '/usr/bin/zcat';
if (str_ends_with($path, '.xz')) return '/usr/bin/xzcat';
if (str_ends_with($path, '.zst')) return '/usr/bin/zstdcat';
@@ -31,8 +29,14 @@ function open_log_stream(string $path) {
}
function close_log_stream(array $h): void {
- fclose($h['fd']);
- proc_close($h['proc']);
+ // Kill the decompressor first so proc_close doesn't block waiting on it
+ // when we abandoned the read early (client aborted, error truncation, etc.).
+ // is_resource() guards because PHP 8 throws TypeError (not a warning) when
+ // these are called on an already-closed handle, so a double-close — e.g.
+ // close + shutdown hook — would otherwise blow up.
+ if (is_resource($h['proc'])) { @proc_terminate($h['proc'], 15); }
+ if (is_resource($h['fd'])) { @fclose($h['fd']); }
+ if (is_resource($h['proc'])) { @proc_close($h['proc']); }
}
function format_size(int $bytes): string {
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/projects/buildlogs.git/commitdiff/c54450c17c6fa4b5584fbbb51f589e7809508a04
More information about the pld-cvs-commit
mailing list