diff --git a/src/lib/testsuite/test_select.c b/src/lib/testsuite/test_select.c index cfd475f..47250eb 100644 --- a/src/lib/testsuite/test_select.c +++ b/src/lib/testsuite/test_select.c @@ -6,6 +6,7 @@ */ #define UNIT_TEST 1 +#include #include #include #include @@ -2252,6 +2253,52 @@ test_sel_register_alloc_thread(void) sel_optimize_test_reset(); } +/* ── Task 8: wrong-thread ownership guard test ───────────────────────────*/ + +/* shared state for the wrong-thread sweep test */ +static int g_wrong_thread_sweep_ran = 0; + +static void* wrong_thread_sweep(void* arg) +{ + (void)arg; + /* calling gc_sweep from the wrong thread must hit the ownership guard + * and return without sweeping — nodes must remain intact */ + gc_sweep_select_nodes(NULL, 0); + g_wrong_thread_sweep_ran = 1; + return NULL; +} + +static void +test_sel_wrong_thread_guard(void) +{ + sel_optimize_test_reset(); + sel_optimize_set(1); + sel_node_table_init(); + + /* Register THIS thread as the allocator */ + sel_register_alloc_thread(); + + /* Allocate a node that should survive the wrong-thread sweep */ + xce_select_node_t* n = makeSelectNode(SEL_TRUE, 0, 0); + TEST_ASSERT_NOT_NULL(n); + + /* Spawn a different thread to call gc_sweep — guard must block it */ + pthread_t tid; + g_wrong_thread_sweep_ran = 0; + int rc = pthread_create(&tid, NULL, wrong_thread_sweep, NULL); + TEST_ASSERT_EQUAL_INT(0, rc); + pthread_join(tid, NULL); + TEST_ASSERT_EQUAL_INT(1, g_wrong_thread_sweep_ran); + + /* Node must still exist (wrong-thread sweep was blocked) */ + /* GC from correct thread: mark then sweep normally */ + gc_clear_marks(); + gc_sweep_select_nodes(NULL, 0); + + sel_node_table_fini(); + sel_optimize_test_reset(); +} + /* ── Top-level runner ─────────────────────────────────────────────────────*/ void @@ -2365,6 +2412,7 @@ test_select(void) /* ── Task 8: sel_alloc_thread_id ownership check ─────────────────── */ RUN_TEST(test_sel_register_alloc_thread); + RUN_TEST(test_sel_wrong_thread_guard); xstring_table_fini(); } diff --git a/src/lib/xce_select.c b/src/lib/xce_select.c index 3c9920a..f8b5f39 100644 --- a/src/lib/xce_select.c +++ b/src/lib/xce_select.c @@ -23,8 +23,9 @@ /* ── optimize level ───────────────────────────────────────────────────── */ -static _Atomic int g_sel_optimize_level = -1; /* -1 = unset */ -static pthread_t sel_alloc_thread_id; /* 0 = unregistered — skip checks */ +static _Atomic int g_sel_optimize_level = -1; /* -1 = unset */ +static pthread_t sel_alloc_thread_id; +static _Atomic int sel_alloc_thread_registered = 0; /* 1 after sel_register_alloc_thread() */ void sel_optimize_set(int level) { @@ -186,12 +187,14 @@ void sel_optimize_test_reset(void) { sel_node_table_fini(); atomic_store_explicit(&g_sel_optimize_level, -1, memory_order_seq_cst); - sel_alloc_thread_id = 0; + atomic_store_explicit(&sel_alloc_thread_registered, 0, memory_order_seq_cst); + /* sel_alloc_thread_id left as-is; only checked when registered flag is set */ } void sel_register_alloc_thread(void) { sel_alloc_thread_id = pthread_self(); + atomic_store_explicit(&sel_alloc_thread_registered, 1, memory_order_release); } /* ── node construction ───────────────────────────────────────────────── */ @@ -651,7 +654,8 @@ int eval_select(const xce_select_node_t* node, select_eval_ctx_t* ctx) if (sel_get_optimize_level() <= 0) { ERROR("eval_select called with optimize_level <= 0"); abort(); } - if (sel_alloc_thread_id != 0 && !pthread_equal(pthread_self(), sel_alloc_thread_id)) { + if (atomic_load_explicit(&sel_alloc_thread_registered, memory_order_acquire) + && !pthread_equal(pthread_self(), sel_alloc_thread_id)) { ERROR("eval_select called from non-allocator thread"); return -1; } @@ -875,7 +879,8 @@ void gc_sweep_select_nodes(const xce_sched_job_t** live_jobs, size_t njobs) if (sel_node_table.table == NULL) { return; } - if (sel_alloc_thread_id != 0 && !pthread_equal(pthread_self(), sel_alloc_thread_id)) { + if (atomic_load_explicit(&sel_alloc_thread_registered, memory_order_acquire) + && !pthread_equal(pthread_self(), sel_alloc_thread_id)) { ERROR("gc_sweep_select_nodes called from non-allocator thread"); return; } diff --git a/src/scheduler/src/main.c b/src/scheduler/src/main.c index 5cdd30e..29d4bb3 100644 --- a/src/scheduler/src/main.c +++ b/src/scheduler/src/main.c @@ -117,16 +117,11 @@ int main(int argc, char* argv[]) INFO("xce scheduler is running"); + /* Register this thread as the sole eval/GC allocator thread (write-once) */ + sel_register_alloc_thread(); + while (1) { sched_update(&sched); - - /* Register this thread as the sole eval/GC allocator thread (write-once) */ - static int sel_thread_registered = 0; - if (!sel_thread_registered) { - sel_register_alloc_thread(); - sel_thread_registered = 1; - } - sched_begin_session(&sched); sched_go(&sched);