[PATCH] Add threaded variants of single-threaded malloc tests
Carlos O'Donell
carlos@redhat.com
Thu Nov 6 14:01:20 GMT 2025
On 11/4/25 12:23 PM, Arjun Shankar wrote:
> Single-threaded malloc tests exercise only the SINGLE_THREAD_P paths in
> the malloc implementation. This commit runs variants of these tests in
> a multi-threaded environment in order to exercise the alternate code
> paths in the same test scenarios, thus potentially improving coverage.
>
> $(test)-threaded-main and $(test)-threaded-worker variants are
> introduced for most single-threaded malloc tests (with a small number of
> exceptions). The -main variants run the base test in a main thread
> while the test environment has an alternate thread running, whereas the
> -worker variants run the test in an alternate thread while the main
> thread waits on it.
>
> Test support/ infrastructure is extended to be able to allow threaded
> execution of these tests without needing to modify the tests themselves,
> using a compile-time -D flag to enable threaded execution instead.
>
> Tests that for any reason currently fail are excluded. They should be
> investigated and followed-up on.
> ---
> I had trouble figuring out how to compile the alternative versions (i.e.
> malloc/Makefile compile recipes. An LLM based code assistant was able to
> come up with the correct lines, so those lines aren't originally written by
> me. I hope this is fine.)
Stepping in here as GNU Project maintainer for glibc.
The Makefile changes you have here are completely mechanical.
Wether you came up with them by looking at other Makefiles, or by talking
to someone, or by conversing with an LLM, these changes are straight forward
and I would accept them without you needing to add anything to the commit.
I'd like to see your changes included in the project without waiting on the
decisions around an AI policy.
Given that we're starting to see AI usage (and I just did a review with
Claude Code), I'm going to prioritize the policy writeup.
> ---
> malloc/Makefile | 43 +++++++++++
> support/Makefile | 1 +
> support/support_test_in_thread_wrapper.c | 90 ++++++++++++++++++++++++
> support/support_test_main.c | 25 +++++--
> support/test-driver.c | 8 +++
> support/test-driver.h | 10 +++
> 6 files changed, 171 insertions(+), 6 deletions(-)
> create mode 100644 support/support_test_in_thread_wrapper.c
>
> diff --git a/malloc/Makefile b/malloc/Makefile
> index cc012e2921..39dc681eda 100644
> --- a/malloc/Makefile
> +++ b/malloc/Makefile
> @@ -293,10 +293,53 @@ $(addprefix $(objpfx), $(tests-link-with-libpthread)): $(shared-thread-library)
> $(tests-link-with-libpthread:%=$(objpfx)%-malloc-largetcache): \
> $(shared-thread-library)
>
> +# tests-threaded-{main,worker} are variants of base malloc tests that are
> +# run in a multi-threaded program. The main variant is run in the main
> +# thread while an alternate thread exists, and the worker variant is run in
> +# a worker thread while the main thread waits on it.
OK.
> +
> +tests-exclude-threaded = \
> + $(tests-link-with-libpthread) \
> + $(tests-static) \
> + tst-compathooks-off \
> + tst-compathooks-on \
> + tst-interpose-nothread \
> + tst-malloc-usable \
> + tst-malloc-usable-tunables \
> + tst-mallocstate \
> + tst-memalign-2 \
> + tst-mxfast \
> + tst-safe-linking \
> +# tests-exclude-threaded
OK. Straight forward list of tests to exclude.
> +
> +tests-threaded-main = $(addsuffix -threaded-main, \
> + $(filter-out $(tests-exclude-threaded), \
> + $(tests)))
OK. Add all the tests to tests-threaded-main, but exclude some which can't
be run in threaded mode.
> +$(addprefix $(objpfx), $(tests-threaded-main)): $(shared-thread-library)
> +
> +tests-threaded-worker = $(addsuffix -threaded-worker, \
> + $(filter-out $(tests-exclude-threaded) \
> + tst-free-errno \
> + tst-mallocfork, \
> + $(tests)))
> +$(addprefix $(objpfx), $(tests-threaded-worker)): $(shared-thread-library)
OK. Likewise.
> +
> +tests += $(tests-threaded-main) $(tests-threaded-worker)
OK. Add tests.
> +
> # These should be removed by `make clean'.
> extra-objs = mcheck-init.o libmcheck.a
> others-extras = mcheck-init.o
>
> +# Compile threaded-{main,worker} tests from their corresponding base
> +# sources, but with -DTEST_IN_THREAD appropriately defined so that support
> +# infrastructure runs them in a multi-threaded program.
OK. Similar to -DTEST_NO_MALLOPT.
> +$(objpfx)%-threaded-main.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_MAIN
> +$(objpfx)%-threaded-main.o: %.c $(before-compile)
> + $(compile-command.c)
> +$(objpfx)%-threaded-worker.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_WORKER
> +$(objpfx)%-threaded-worker.o: %.c $(before-compile)
> + $(compile-command.c)
OK. For all the addsuffix targets we need valid targets to compile the original
C file with the suffix removed and a new compiler flag added.
All of this is strictly mechanical Makefile changes following what we already
do in other files.
> +
> # Include the cleanup handler.
> aux := set-freeres thread-freeres
>
> diff --git a/support/Makefile b/support/Makefile
> index 2043e4e590..a7b1b39782 100644
> --- a/support/Makefile
> +++ b/support/Makefile
> @@ -98,6 +98,7 @@ libsupport-routines = \
> support_test_compare_failure \
> support_test_compare_string \
> support_test_compare_string_wide \
> + support_test_in_thread_wrapper \
> support_test_main \
> support_test_verify_impl \
> support_wait_for_thread_exit \
> diff --git a/support/support_test_in_thread_wrapper.c b/support/support_test_in_thread_wrapper.c
> new file mode 100644
> index 0000000000..a4d5df0c12
> --- /dev/null
> +++ b/support/support_test_in_thread_wrapper.c
> @@ -0,0 +1,90 @@
> +/* Test-in-thread wrapper function for the test driver.
> + 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/>. */
> +
> +#include <support/test-driver.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/xthread.h>
> +
> +struct test_thread_args
> +{
> + int argc;
> + char **argv;
> + const struct test_config *config;
> + int ret;
> +};
> +
> +static void *
> +test_thread (void *closure)
> +{
> + struct test_thread_args *args = closure;
> +
> + if (args->config->test_function != NULL)
> + args->ret = args->config->test_function ();
> + else if (args->config->test_function_argv != NULL)
> + args->ret = args->config->test_function_argv (args->argc, args->argv);
> + else
> + {
> + printf ("error: no test function defined\n");
> + exit (1);
> + }
> + return NULL;
> +}
> +
> +pthread_barrier_t barrier;
> +
> +static void *
> +empty_thread (void *closure)
> +{
> + /* Make sure that the alternate thread waits (and exists) till the main
> + thread finishes running the test. */
> + xpthread_barrier_wait (&barrier);
> +
> + return NULL;
> +}
> +
> +int
> +support_test_in_thread_wrapper (int argc, char **argv,
> + const struct test_config *config)
> +{
> + pthread_t thread;
> + struct test_thread_args closure = {.argc = argc,
> + .argv = argv,
> + .config = config};
> +
> + if (config->test_in_thread == THREAD_MAIN)
> + {
> + xpthread_barrier_init (&barrier, NULL, 2);
> + thread = xpthread_create (NULL, empty_thread, NULL);
> +
> + /* Run the test in the main thread. */
> + test_thread (&closure);
> +
> + /* Signal to the alternate thread that it can return. */
> + xpthread_barrier_wait (&barrier);
> + xpthread_join (thread);
> + }
> + else /* config->test_in_thread == THREAD_WORKER. */
> + {
> + /* Run the test in an alternate thread. */
> + thread = xpthread_create (NULL, test_thread, &closure);
> + xpthread_join (thread);
> + }
> +
> + return closure.ret;
> +}
> diff --git a/support/support_test_main.c b/support/support_test_main.c
> index 1558e27c57..baca343cda 100644
> --- a/support/support_test_main.c
> +++ b/support/support_test_main.c
> @@ -238,14 +238,27 @@ run_test_function (int argc, char **argv, const struct test_config *config)
> exit (1);
> }
>
> - if (config->test_function != NULL)
> - return config->test_function ();
> - else if (config->test_function_argv != NULL)
> - return config->test_function_argv (argc, argv);
> + if (config->test_in_thread == 0)
> + {
> + if (config->test_function != NULL)
> + return config->test_function ();
> + else if (config->test_function_argv != NULL)
> + return config->test_function_argv (argc, argv);
> + else
> + {
> + printf ("error: no test function defined\n");
> + exit (1);
> + }
> + }
> else
> {
> - printf ("error: no test function defined\n");
> - exit (1);
> + if (config->test_in_thread_wrapper != NULL)
> + return config->test_in_thread_wrapper (argc, argv, config);
> + else
> + {
> + printf ("error: no test-in-thread wrapper defined\n");
> + exit (1);
> + }
> }
> }
>
> diff --git a/support/test-driver.c b/support/test-driver.c
> index 40e2bc0ee2..cb65b9f078 100644
> --- a/support/test-driver.c
> +++ b/support/test-driver.c
> @@ -168,5 +168,13 @@ main (int argc, char **argv)
> test_config.optstring = "+";
> #endif
>
> +#ifdef TEST_IN_THREAD
> + test_config.test_in_thread = TEST_IN_THREAD;
> + test_config.test_in_thread_wrapper = support_test_in_thread_wrapper;
> +#else
> + test_config.test_in_thread = 0;
> + test_config.test_in_thread_wrapper = NULL;
> +#endif
> +
> return support_test_main (argc, argv, &test_config);
> }
> diff --git a/support/test-driver.h b/support/test-driver.h
> index 9291611fbf..2d76ba4aab 100644
> --- a/support/test-driver.h
> +++ b/support/test-driver.h
> @@ -38,6 +38,8 @@ struct test_config
> char no_setvbuf; /* Boolean flag to disable setvbuf. */
> char run_command_mode; /* Boolean flag to indicate run-command-mode. */
> const char *optstring; /* Short command line options. */
> + int test_in_thread; /* 0 => no threading, MAIN, WORKER. */
> + int (*test_in_thread_wrapper) (int, char **, const struct test_config *);
> };
>
> enum
> @@ -54,6 +56,11 @@ enum
> /* Used for command line argument parsing. */
> OPT_DIRECT = 1000,
> OPT_TESTDIR,
> +
> + /* Used for TEST_IN_THREAD to run single-threaded tests in a
> + multi-threaded environment. */
> + THREAD_MAIN = 1,
> + THREAD_WORKER = 2,
> };
>
> /* Options provided by the test driver. */
> @@ -78,6 +85,9 @@ extern unsigned int test_verbose;
> printf (__VA_ARGS__); \
> } while (0);
>
> +extern int support_test_in_thread_wrapper (int argc, char **argv,
> + const struct test_config *config);
> +
> int support_test_main (int argc, char **argv, const struct test_config *);
>
> __END_DECLS
--
Cheers,
Carlos.
More information about the Libc-alpha
mailing list