Commit f6c146ffab for openssl.org
commit f6c146ffab8ffc17b3f4e3fd6061e6c8c5fe8d56
Author: Bob Beck <beck@openssl.org>
Date: Tue Aug 18 18:35:03 2026 -0600
Add a watchdog timeout to the C tests built with testutil/main
This adds a timeout that will self-terminate a test with a
sigabrt after a timeout - in the hope that if a test is wedged
we now get diagnostic information (like a core dump or stack trace)
instead of the CI job timing out globally
The default timeout for a single C test is 30 minutes, which should
be plenty, and it is configurable via environment variable
documented in the test README.md file
This does nothing to the invocation yet to ensure we can get a
core dump.
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
MergeDate: Fri Sep 11 19:12:58 2026
(Merged from https://github.com/openssl/openssl/pull/32424)
diff --git a/test/README.md b/test/README.md
index 1575fb69dc..4437f15436 100644
--- a/test/README.md
+++ b/test/README.md
@@ -149,6 +149,29 @@ To run up to four tests in parallel at any given time:
$ make HARNESS_JOBS=4 test
+Test time limit
+---------------
+
+Each test program is run under a watchdog so that a test which hangs is
+identified rather than silently stalling the run. If a test program runs for
+longer than a fixed time limit the watchdog assumes it has hung and aborts it
+(via `abort()`). The abort marks that specific test as failed - so a hang is
+attributed to the test responsible rather than appearing as an anonymous
+stalled run - and, where the environment is configured to produce them, leaves
+a core dump capturing every thread's stack, so it can be seen where the test
+was wedged.
+
+The limit defaults to 1800 seconds (30 minutes) per test program and can be
+changed with the `OPENSSL_TEST_TIMEOUT` environment variable, which gives the
+limit in seconds. Setting it to `0` (or a negative value) disables the
+watchdog entirely.
+
+ $ make OPENSSL_TEST_TIMEOUT=600 test
+
+The limit applies to each individual test program, not to the test run as a
+whole. It only covers test programs built on the test framework; helper
+invocations of the `openssl` application are not affected.
+
Random numbers in tests
-----------------------
diff --git a/test/testutil/main.c b/test/testutil/main.c
index 940d25f707..65b3284892 100644
--- a/test/testutil/main.c
+++ b/test/testutil/main.c
@@ -7,6 +7,9 @@
* https://www.openssl.org/source/license.html
*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
#include <openssl/crypto.h>
#include "../testutil.h"
#include "output.h"
@@ -20,6 +23,94 @@
#endif
#endif /* defined(__has_include) */
+/*
+ * Watchdog: abort a test program that runs longer than a fixed time limit.
+ * A hung test otherwise consumes the entire CI job budget (e.g. 6 hours); the
+ * watchdog turns that into a prompt failure that names the test and, where the
+ * environment enables core dumps, leaves a core with every thread's stack for
+ * diagnosis. The limit is TEST_WATCHDOG_TIMEOUT seconds, overridable with the
+ * OPENSSL_TEST_TIMEOUT environment variable; a value of 0 or less disables it.
+ *
+ * The default must exceed the slowest legitimate testutil program on the
+ * slowest supported target (app-based recipes have their own main() and are
+ * not covered here). Under emulated hppa the slowest such program runs well
+ * under ten minutes, so thirty minutes leaves ample margin.
+ */
+#define TEST_WATCHDOG_TIMEOUT 1800
+
+#if !defined(OPENSSL_SYS_WINDOWS)
+#include <unistd.h>
+#include <signal.h>
+
+/*
+ * The message is prepared at arm time (with the actual limit) so that the
+ * signal handler only has to call write(), which is async-signal-safe;
+ * snprintf() is not.
+ */
+static char watchdog_msg[128];
+static size_t watchdog_msg_len;
+
+static void test_watchdog_expired(int sig)
+{
+ (void)sig;
+ if (write(STDERR_FILENO, watchdog_msg, watchdog_msg_len) < 0) {
+ }
+ abort();
+}
+
+static void test_watchdog_start(unsigned int timeout)
+{
+ int n = snprintf(watchdog_msg, sizeof(watchdog_msg),
+ "\n# test watchdog: exceeded time limit of %u seconds, aborting\n",
+ timeout);
+
+ if (n < 0)
+ n = 0;
+ else if ((size_t)n >= sizeof(watchdog_msg))
+ n = sizeof(watchdog_msg) - 1;
+ watchdog_msg_len = (size_t)n;
+
+ signal(SIGALRM, test_watchdog_expired);
+ alarm(timeout);
+}
+#else
+#include <stdint.h>
+#include <windows.h>
+
+static DWORD WINAPI test_watchdog_thread(LPVOID arg)
+{
+ unsigned int timeout = (unsigned int)(uintptr_t)arg;
+
+ Sleep((DWORD)timeout * 1000);
+ fprintf(stderr,
+ "\n# test watchdog: exceeded time limit of %u seconds, aborting\n",
+ timeout);
+ fflush(stderr);
+ abort();
+ return 0;
+}
+
+static void test_watchdog_start(unsigned int timeout)
+{
+ CreateThread(NULL, 0, test_watchdog_thread,
+ (LPVOID)(uintptr_t)timeout, 0, NULL);
+}
+#endif /* !defined(OPENSSL_SYS_WINDOWS) */
+
+static void setup_test_watchdog(void)
+{
+ long timeout = TEST_WATCHDOG_TIMEOUT;
+ const char *e = getenv("OPENSSL_TEST_TIMEOUT");
+
+ if (e != NULL && *e != '\0')
+ timeout = strtol(e, NULL, 10);
+ if (timeout <= 0)
+ return;
+ if ((unsigned long)timeout > UINT_MAX)
+ timeout = UINT_MAX;
+ test_watchdog_start((unsigned int)timeout);
+}
+
/*
* At some point we should consider looking at this function with a view to
* moving most/all of this into onfree handlers in OSSL_LIB_CTX.
@@ -40,6 +131,8 @@ int main(int argc, char *argv[])
test_open_streams();
+ setup_test_watchdog();
+
if (!gi_ret) {
test_printf_stderr("Global init failed - aborting\n");
return ret;