[PATCH 1/2] support: Add support_accept_oom to heuristically support OOM errors
Carlos O'Donell
carlos@redhat.com
Tue Sep 9 12:26:42 GMT 2025
On 6/3/25 12:46 PM, Florian Weimer wrote:
> Some tests may trigger the kernel OOM handler under conditions
> which are difficult to predict (depending on available RAM and
> swap space). If we can determine specific regions which might
> do this and this does not contradict the test object, the
> functions support_accept_oom (true) and support_accept_oom (false)
> can be called at the start and end, and the test driver will
> ignore SIGKILL signals.
LGTM.
Please feel free to include my RB if you make the two textual
suggestions.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> ---
> support/Makefile | 1 +
> support/check.h | 4 +-
> support/support.h | 9 +++
> support/support_record_failure.c | 36 ++++++++++
> support/support_test_main.c | 19 +++++
> support/tst-support_accept_oom.c | 115 +++++++++++++++++++++++++++++++
> 6 files changed, 183 insertions(+), 1 deletion(-)
> create mode 100644 support/tst-support_accept_oom.c
>
> diff --git a/support/Makefile b/support/Makefile
> index d41278eeab..eb3b5b9be9 100644
> --- a/support/Makefile
> +++ b/support/Makefile
> @@ -332,6 +332,7 @@ tests = \
> tst-support-open-dev-null-range \
> tst-support-openpty \
> tst-support-process_state \
> + tst-support_accept_oom \
OK. Correct alpha-sort order. Adds new test for functions.
> tst-support_blob_repeat \
> tst-support_capture_subprocess \
> tst-support_descriptors \
> diff --git a/support/check.h b/support/check.h
> index 49db05aab5..91fedae9db 100644
> --- a/support/check.h
> +++ b/support/check.h
> @@ -196,9 +196,11 @@ void support_test_compare_string_wide (const wchar_t *left,
> const char *left_expr,
> const char *right_expr);
>
> -/* Internal function called by the test driver. */
> +/* Internal functions called by the test driver. */
> int support_report_failure (int status)
> __attribute__ ((weak, warn_unused_result));
> +int support_is_oom_accepted (void)
OK. Adds a new function to verify if OOM *could* be accepted.
Overall the OOM SIGKILL will terminate the test and it will fail.
Instead we want to turn this failure into an UNUSPPORTED result.
> + __attribute__ ((weak, warn_unused_result));
>
> /* Internal function used to test the failure recording framework. */
> void support_record_failure_reset (void);
> diff --git a/support/support.h b/support/support.h
> index 4998a34894..84a383d856 100644
> --- a/support/support.h
> +++ b/support/support.h
> @@ -239,6 +239,15 @@ int support_open_dev_null_range (int num, int flags, mode_t mode);
> /* Check if kernel supports set VMA range name. */
> extern bool support_set_vma_name_supported (void);
>
> +/* If invoked with a true argument, instructions to supervising
Suggest:
s/instructions to/it instructs the/g
> + process to ignore unexpected termination of the test process,
> + likely due to an OOM error. (This can theoretically mask other
> + test errors, so it should be used sparingly.)
> +
> + If invoked with a false argument, the default behavior is restored,
> + and OOM-induced errors result in test failure. */
> +void support_accept_oom (bool);
> +
> __END_DECLS
>
> #endif /* SUPPORT_H */
> diff --git a/support/support_record_failure.c b/support/support_record_failure.c
> index c69be203c0..6ed5ca4529 100644
> --- a/support/support_record_failure.c
> +++ b/support/support_record_failure.c
> @@ -31,6 +31,10 @@
> failure is detected, so that even if the counter wraps around to
> zero, the failure of a test can be detected.
>
> + If the accept_oom member is not zero, the supervisor process will
> + use heuristics to suppress process termination due to OOM
> + conditions.
> +
> The init constructor function below puts *state on a shared
> anonymous mapping, so that failure reports from subprocesses
> propagate to the parent process. */
> @@ -38,6 +42,7 @@ struct test_failures
> {
> unsigned int counter;
> unsigned int failed;
> + unsigned int accept_oom;
> };
> static struct test_failures *state;
>
> @@ -122,3 +127,34 @@ support_record_failure_barrier (void)
> exit (1);
> }
> }
> +
> +void
> +support_accept_oom (bool onoff)
> +{
> + if (onoff)
> + {
> + /* One thread detects the overflow. */
> + if (__atomic_fetch_add (&state->accept_oom, 1, __ATOMIC_RELAXED)
> + == UINT_MAX)
> + {
> + puts ("error: OOM acceptance counter overflow");
> + exit (1);
> + }
> + }
> + else
> + {
> + /* One thread detects the underflow. */
> + if (__atomic_fetch_add (&state->accept_oom, -1, __ATOMIC_RELAXED)
> + == 0)
> + {
> + puts ("error: OOM acceptance counter underflow");
> + exit (1);
> + }
> + }
> +}
OK. Sets accept_oom to positive if we are to convert OOM to UNSUPPORTED.
> +
> +int
> +support_is_oom_accepted (void)
> +{
> + return __atomic_load_n (&state->accept_oom, __ATOMIC_RELAXED) != 0;
> +}
OK. If accept_oom is not zero we return true, because we are in a narrow
region where OOM is acceptable due to some test condition, and we will likely
turn it into UNSUPPORTED.
> diff --git a/support/support_test_main.c b/support/support_test_main.c
> index bd6c728f1c..c2caefa6f5 100644
> --- a/support/support_test_main.c
> +++ b/support/support_test_main.c
> @@ -264,6 +264,20 @@ adjust_exit_status (int status)
> return status;
> }
>
> +/* Return true if the exit status looks like it may have been
> + triggered by kernel OOM handling, and support_accept_oom (true) was
> + active in the test process. This is a very approximate check.
> + Unfortunately, the SI_KERNEL value for si_code in siginfo_t is not
> + observable via waitid (it gets translated to CLD_KILLED*/
Suggest:
s/CLD_KILLED/CLD_KILLED). *\//g
> +static bool
> +accept_oom_heuristic (int status)
> +{
> + return (WIFSIGNALED (status)
> + && WTERMSIG (status) == SIGKILL
> + && support_is_oom_accepted != NULL
> + && support_is_oom_accepted ());
> +}
> +
> int
> support_test_main (int argc, char **argv, const struct test_config *config)
> {
> @@ -497,6 +511,11 @@ support_test_main (int argc, char **argv, const struct test_config *config)
> /* Process was killed by timer or other signal. */
> else
> {
> + if (accept_oom_heuristic (status))
> + {
> + puts ("Heuristically determined OOM termination; SIGKILL ignored");
> + exit (adjust_exit_status (EXIT_UNSUPPORTED));
OK. Convert OOM failures into UNSUPPORTED due to not meeting memory requirements for the test.
This is possible if there isn't enough for testing certain memory intensive tests.
> + }
> if (config->expected_signal == 0)
> {
> printf ("Didn't expect signal from child: got `%s'\n",
> diff --git a/support/tst-support_accept_oom.c b/support/tst-support_accept_oom.c
> new file mode 100644
> index 0000000000..42a4328cbc
> --- /dev/null
> +++ b/support/tst-support_accept_oom.c
> @@ -0,0 +1,115 @@
> +/* Test that OOM error suppression works.
> + Copyright (C) 2025 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +/* This test reacts to the reject_oom and inject_error environment
> + variables. It is never executed automatically because it can run
> + for a very long time on large systems, and is generally stressful
> + to the system. */
OK. When run with oom_test_active and reject_oom, we fail the test on
OOM (fail on unexpected signal).
OK. When run with oom_test_active, we pass the test with UNUSPPORTED
on OOM.
OK. When run with inject_error, we ignore the OOM failure, but log
the injected error (this is the purpose of adjust_exit_status()).
OK, When run with inject_error, and reject_oom, we fail the test with
the test with an unexpected signal.
OK, When run without any options (Default) we pass the test.
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support.h>
> +#include <support/check.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +
> +/* If true, support_accept_oom is called. */
> +static bool accept_oom;
> +
> +/* System page size. Allocations are always at least that large. */
> +static size_t page_size;
> +
> +/* All allocated bytes. */
> +static size_t total_bytes;
> +
> +/* Try to allocate SIZE bytes of memory, and ensure that is backed by
> + actual memory. */
> +static bool
> +populate_memory (size_t size)
> +{
> + TEST_COMPARE (size % page_size, 0);
> + char *ptr = mmap (NULL, size, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + if (ptr == MAP_FAILED)
> + return false;
> +
> + if (accept_oom)
> + support_accept_oom (true);
> +
> + /* Ensure that the kernel allocates backing storage. Make the pages
> + distinct using the total_bytes counter. */
> + for (size_t offset = 0; offset < size; offset += page_size)
> + {
> + memcpy (ptr + offset, &total_bytes, sizeof (total_bytes));
> + total_bytes += page_size;
> + }
> +
> + if (accept_oom)
> + support_accept_oom (false);
> +
> + return true;
> +}
> +
> +static int
> +do_test (void)
> +{
> + if (getenv ("oom_test_active") == NULL)
> + {
> + puts ("info: This test does nothing by default.");
> + puts ("info: Set the oom_test_active environment variable to enable it.");
> + puts ("info: Consider testing with inject_error and reject_oom as well.");
> + return 0;
> + }
> +
> + accept_oom = getenv ("reject_oom") == NULL;
> +
> + page_size = sysconf (_SC_PAGESIZE);
> + size_t size = page_size;
> +
> + /* The environment variable can be set to trigger a test failure.
> + The OOM event should not obscure this error. */
> + TEST_COMPARE_STRING (getenv ("inject_error"), NULL);
> +
> + /* Grow the allocation until allocation fails. */
> + while (true)
> + {
> + size_t new_size = 2 * size;
> + if (new_size == 0 || !populate_memory (new_size))
> + break;
> + size = new_size;
> + }
> +
> + while (true)
> + {
> + if (!populate_memory (size))
> + {
> + /* Decrease size and see if the allocation succeeds. */
> + size /= 2;
> + if (size < page_size)
> + FAIL_UNSUPPORTED ("could not trigger OOM"
> + " after allocating %zu bytes",
> + total_bytes);
> + }
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
>
> base-commit: d475e5bf4f1e0628354c45fb830388a92f7d1181
--
Cheers,
Carlos.
More information about the Libc-alpha
mailing list