From f4f1be9e77174a9802d5ce86d5846e3b091cac1b Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 27 Apr 2026 09:57:17 +0200 Subject: [PATCH] chore(test-runner): add -k kill-after to timeout so wedged jperl is hard-killed GNU `timeout` only sends SIGTERM by default. If jperl/JVM ignores or hangs in response to SIGTERM (e.g. blocked in a JNI call or a stuck shutdown hook), the wrapped process can outlive the configured timeout indefinitely -- we observed an `op/gv.t` worker still running ~10 hours after a 300s timeout. - Pass `-k 10s` to both `timeout` and `gtimeout` so SIGKILL follows SIGTERM after a short grace period. - Skip detection on Windows ($^O = MSWin32/cygwin/msys); Windows' built-in `timeout.exe` is a sleep-with-countdown, not GNU timeout, so calling it as `timeout 300s ...` would be wrong. Windows keeps using the alarm-based fallback. - Treat exit codes 137 (128+SIGKILL) and 143 (128+SIGTERM) as 'timeout' status alongside 124, so `-k`-killed processes are still classified as timeouts rather than generic errors. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- dev/tools/perl_test_runner.pl | 29 ++++++++++++++----- .../org/perlonjava/core/Configuration.java | 6 ++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/dev/tools/perl_test_runner.pl b/dev/tools/perl_test_runner.pl index 204233c09..b78ef38ae 100755 --- a/dev/tools/perl_test_runner.pl +++ b/dev/tools/perl_test_runner.pl @@ -315,13 +315,23 @@ sub run_single_test { my $abs_jperl = File::Spec->rel2abs($jperl_path, $old_dir); my $test_name = File::Spec->abs2rel($test_file, $local_test_dir || '.'); - # Try to use system timeout command if available + # Try to use system timeout command if available. + # Use --kill-after (-k) so a SIGTERM that the JVM ignores is followed + # by SIGKILL after a grace period; otherwise wedged jperl processes + # outlive the timeout. GNU coreutils `timeout` (Linux, macOS via + # homebrew/gtimeout) supports -k. Windows ships a `timeout.exe` that is + # NOT GNU timeout (it's a sleep-with-countdown), so we skip detection + # there and fall back to the alarm-based path. my $timeout_cmd = ''; - if (system('which timeout >/dev/null 2>&1') == 0) { - $timeout_cmd = "timeout ${timeout}s "; - } elsif (system('which gtimeout >/dev/null 2>&1') == 0) { - # macOS with coreutils - $timeout_cmd = "gtimeout ${timeout}s "; + my $is_windows = ($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys'); + my $kill_after = 10; # seconds between SIGTERM and SIGKILL + if (!$is_windows) { + if (system('which timeout >/dev/null 2>&1') == 0) { + $timeout_cmd = "timeout -k ${kill_after}s ${timeout}s "; + } elsif (system('which gtimeout >/dev/null 2>&1') == 0) { + # macOS with coreutils + $timeout_cmd = "gtimeout -k ${kill_after}s ${timeout}s "; + } } my $cmd = "${timeout_cmd}$abs_jperl $test_name 2>&1"; @@ -358,8 +368,11 @@ sub run_single_test { # Restore directory chdir($old_dir); - # Check if it was a timeout - if ($exit_code == 124) { + # Check if it was a timeout. + # 124 = GNU timeout sent SIGTERM and child exited. + # 137 = 128 + SIGKILL (9), child was hard-killed by `timeout -k`. + # 143 = 128 + SIGTERM (15), child exited due to SIGTERM. + if ($exit_code == 124 || $exit_code == 137 || $exit_code == 143) { return { status => 'timeout', ok_count => 0, not_ok_count => 0, total_tests => 0, diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 48a80f664..2a9b151de 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,14 +33,14 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "ba8021aed"; + public static final String gitCommitId = "388dc0d02"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitDate = "2026-04-26"; + public static final String gitCommitDate = "2026-04-27"; /** * Build timestamp in Perl 5 "Compiled at" format (e.g., "Apr 7 2026 11:20:00"). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 26 2026 23:11:17"; + public static final String buildTimestamp = "Apr 27 2026 09:57:34"; // Prevent instantiation private Configuration() {