| /*****************************************************************************\ |
| * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| \*****************************************************************************/ |
| |
| #define _GNU_SOURCE |
| #include <check.h> |
| |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/read_config.h" |
| #include "src/common/slurm_protocol_defs.h" |
| |
| #include "src/common/workerpool.h" |
| |
| #if SLURM_VERSION_NUMBER >= SLURM_VERSION_NUM(26, 11, 0) |
| |
| #define THREAD_COUNT 4 |
| #define WORK_COUNT 8 |
| |
| static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
| static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; |
| |
| /* Total callbacks that have completed */ |
| static int done = 0; |
| /* Callbacks currently executing */ |
| static int active = 0; |
| /* Highest number of callbacks seen running at once */ |
| static int peak = 0; |
| /* Hold blocking callbacks until the test releases them */ |
| static bool release = false; |
| |
| static void _reset(void) |
| { |
| slurm_mutex_lock(&lock); |
| done = 0; |
| active = 0; |
| peak = 0; |
| release = false; |
| slurm_mutex_unlock(&lock); |
| } |
| |
| /* Increment the completion counter and signal the test thread */ |
| static void _count_work(const bool shutdown, void *arg) |
| { |
| slurm_mutex_lock(&lock); |
| done++; |
| slurm_cond_broadcast(&cond); |
| slurm_mutex_unlock(&lock); |
| } |
| |
| /* |
| * Track concurrency: bump active/peak on entry, then block until the test |
| * thread sets release so several callbacks overlap in flight. |
| */ |
| static void _blocking_work(const bool shutdown, void *arg) |
| { |
| slurm_mutex_lock(&lock); |
| active++; |
| if (active > peak) |
| peak = active; |
| slurm_cond_broadcast(&cond); |
| |
| while (!release) |
| slurm_cond_wait(&cond, &lock); |
| |
| active--; |
| done++; |
| slurm_cond_broadcast(&cond); |
| slurm_mutex_unlock(&lock); |
| } |
| |
| static void setup(void) |
| { |
| log_options_t log_opts = LOG_OPTS_INITIALIZER; |
| const char *debug_env = getenv("SLURM_DEBUG"); |
| const char *debug_flags_env = getenv("SLURM_DEBUG_FLAGS"); |
| |
| /* Setup logging */ |
| if (debug_env) |
| log_opts.stderr_level = log_string2num(debug_env); |
| if (debug_flags_env) |
| debug_str2flags(debug_flags_env, &slurm_conf.debug_flags); |
| log_init("workerpool-test", log_opts, 0, NULL); |
| } |
| |
| static void teardown(void) |
| { |
| log_fini(); |
| } |
| |
| START_TEST(test_init_fini) |
| { |
| workerpool_init(0, THREAD_COUNT, NULL); |
| /* Re-init while running is a no-op */ |
| workerpool_init(0, THREAD_COUNT, NULL); |
| |
| workerpool_fini(); |
| /* Double fini must be safe */ |
| workerpool_fini(); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_enqueue) |
| { |
| _reset(); |
| |
| workerpool_init(0, THREAD_COUNT, NULL); |
| |
| for (int i = 0; i < WORK_COUNT; i++) |
| workerpool_enqueue_normal(_count_work, NULL); |
| |
| /* Wait for every callback to complete */ |
| slurm_mutex_lock(&lock); |
| while (done < WORK_COUNT) |
| slurm_cond_wait(&cond, &lock); |
| slurm_mutex_unlock(&lock); |
| |
| ck_assert_int_eq(done, WORK_COUNT); |
| |
| workerpool_fini(); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_concurrency) |
| { |
| _reset(); |
| |
| /* Configure an explicit, known thread count */ |
| workerpool_init(0, 0, "workerpool_threads=" XSTRINGIFY(THREAD_COUNT)); |
| |
| for (int i = 0; i < WORK_COUNT; i++) |
| workerpool_enqueue_normal(_blocking_work, NULL); |
| |
| /* Wait until at least two callbacks overlap, proving real concurrency */ |
| slurm_mutex_lock(&lock); |
| while (active < 2) |
| slurm_cond_wait(&cond, &lock); |
| /* Release every blocked callback */ |
| release = true; |
| slurm_cond_broadcast(&cond); |
| while (done < WORK_COUNT) |
| slurm_cond_wait(&cond, &lock); |
| slurm_mutex_unlock(&lock); |
| |
| ck_assert_int_eq(done, WORK_COUNT); |
| /* Concurrency happened but never exceeded the configured thread count */ |
| ck_assert_int_ge(peak, 2); |
| ck_assert_int_le(peak, THREAD_COUNT); |
| |
| workerpool_fini(); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_conmgr_threads_alias) |
| { |
| _reset(); |
| |
| /* conmgr_threads= must be accepted as an alias for workerpool_threads= */ |
| workerpool_init(0, 0, "conmgr_threads=" XSTRINGIFY(THREAD_COUNT)); |
| |
| for (int i = 0; i < WORK_COUNT; i++) |
| workerpool_enqueue_normal(_count_work, NULL); |
| |
| slurm_mutex_lock(&lock); |
| while (done < WORK_COUNT) |
| slurm_cond_wait(&cond, &lock); |
| slurm_mutex_unlock(&lock); |
| |
| ck_assert_int_eq(done, WORK_COUNT); |
| |
| workerpool_fini(); |
| } |
| |
| END_TEST |
| |
| #endif /* SLURM_VERSION_NUMBER >= SLURM_VERSION_NUM(26,11,0) */ |
| |
| extern int main(int argc, char **argv) |
| { |
| int failures; |
| |
| TCase *tcase = tcase_create("workerpool"); |
| #if SLURM_VERSION_NUMBER >= SLURM_VERSION_NUM(26, 11, 0) |
| tcase_add_unchecked_fixture(tcase, setup, teardown); |
| /* Concurrency test blocks worker threads; allow generous wall time */ |
| tcase_set_timeout(tcase, 60); |
| tcase_add_test(tcase, test_init_fini); |
| tcase_add_test(tcase, test_enqueue); |
| tcase_add_test(tcase, test_concurrency); |
| tcase_add_test(tcase, test_conmgr_threads_alias); |
| #endif |
| |
| Suite *suite = suite_create("workerpool"); |
| suite_add_tcase(suite, tcase); |
| |
| SRunner *sr = srunner_create(suite); |
| srunner_run_all(sr, CK_VERBOSE); |
| failures = srunner_ntests_failed(sr); |
| srunner_free(sr); |
| |
| return failures; |
| } |