Mercurial
diff seobeo/tests/seobeo_worker_test.c @ 250:745fd127b2a1
[seobeo] Add bounded worker interface
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 06:23:37 -0700 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/tests/seobeo_worker_test.c Tue Aug 04 06:23:37 2026 -0700 @@ -0,0 +1,261 @@ +#include "seobeo/seobeo_worker.h" + +#include <assert.h> +#include <stdatomic.h> +#include <stdlib.h> +#include <unistd.h> + +typedef struct { + atomic_int executed; + atomic_int cleaned; + atomic_int entered; + atomic_int release; + atomic_int cleanup_entered; + atomic_int hold_cleanup; +} Worker_Test_Context; + +typedef struct { + Seobeo_Worker_Pool *p_pool; + Seobeo_Worker_Result result; +} Pool_Shutdown_Context; + +typedef struct { + Seobeo_Worker_Pool *p_pool; + atomic_int returned; +} Pool_Wait_Context; + +typedef struct { + Seobeo_Worker_Pool *p_pool; + Seobeo_Worker_Result wait_result; + Seobeo_Worker_Result shutdown_result; + atomic_int cleaned; +} Recursive_Cleanup_Context; + +static void count_task(void *p_context) +{ + Worker_Test_Context *p_test = p_context; + atomic_fetch_add(&p_test->executed, 1); +} + +static void no_op_task(void *p_context) +{ + (void)p_context; +} + +static void blocking_task(void *p_context) +{ + Worker_Test_Context *p_test = p_context; + atomic_fetch_add(&p_test->executed, 1); + atomic_store(&p_test->entered, 1); + while (!atomic_load(&p_test->release)) + usleep(1000); +} + +static void cleanup_task(void *p_context) +{ + Worker_Test_Context *p_test = p_context; + if (atomic_load(&p_test->hold_cleanup)) + { + atomic_store(&p_test->cleanup_entered, 1); + while (!atomic_load(&p_test->release)) + usleep(1000); + } + atomic_fetch_add(&p_test->cleaned, 1); +} + +static void shutdown_pool_task(void *p_context) +{ + Pool_Shutdown_Context *p_shutdown = p_context; + p_shutdown->result = + Seobeo_Worker_Pool_Shutdown(p_shutdown->p_pool, FALSE); +} + +static void wait_pool_task(void *p_context) +{ + Pool_Wait_Context *p_wait = p_context; + assert(Seobeo_Worker_Pool_Wait(p_wait->p_pool) == SEOBEO_WORKER_OK); + atomic_store(&p_wait->returned, 1); +} + +static void recursive_pool_cleanup(void *p_context) +{ + Recursive_Cleanup_Context *p_cleanup = p_context; + p_cleanup->wait_result = + Seobeo_Worker_Pool_Wait(p_cleanup->p_pool); + p_cleanup->shutdown_result = + Seobeo_Worker_Pool_Shutdown(p_cleanup->p_pool, FALSE); + Seobeo_Worker_Pool_Destroy(p_cleanup->p_pool); + atomic_store(&p_cleanup->cleaned, 1); +} + +static void wait_for_value(atomic_int *p_value, int expected) +{ + for (int i = 0; i < 2000; i++) + { + if (atomic_load(p_value) == expected) + return; + usleep(1000); + } + assert(FALSE && "worker operation timed out"); +} + +static void test_joinable_thread(void) +{ + Worker_Test_Context context = {0}; + Seobeo_Thread *p_thread = + Seobeo_Thread_Start(count_task, &context, cleanup_task); + assert(p_thread); + assert(Seobeo_Thread_Join(p_thread) == SEOBEO_WORKER_OK); + assert(atomic_load(&context.executed) == 1); + assert(atomic_load(&context.cleaned) == 1); +} + +static void test_detached_thread(void) +{ + Worker_Test_Context context = {0}; + assert(Seobeo_Thread_Start_Detached( + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + wait_for_value(&context.cleaned, 1); + assert(atomic_load(&context.executed) == 1); +} + +static void test_pool_drain(void) +{ + Worker_Test_Context context = {0}; + Seobeo_Worker_Pool *p_pool = + Seobeo_Worker_Pool_Create(2, 8); + assert(p_pool); + for (int i = 0; i < 8; i++) + { + assert(Seobeo_Worker_Pool_Submit( + p_pool, + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + } + assert(Seobeo_Worker_Pool_Wait(p_pool) == SEOBEO_WORKER_OK); + assert(atomic_load(&context.executed) == 8); + assert(atomic_load(&context.cleaned) == 8); + assert(Seobeo_Worker_Pool_Pending(p_pool) == 0); + assert(Seobeo_Worker_Pool_Active(p_pool) == 0); + assert(Seobeo_Worker_Pool_Shutdown(p_pool, TRUE) == SEOBEO_WORKER_OK); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_STOPPED); + Seobeo_Worker_Pool_Destroy(p_pool); +} + +static void test_pool_queue_limit(void) +{ + Worker_Test_Context context = {0}; + Seobeo_Worker_Pool *p_pool = + Seobeo_Worker_Pool_Create(1, 1); + assert(p_pool); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + blocking_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + wait_for_value(&context.entered, 1); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_QUEUE_FULL); + atomic_store(&context.release, 1); + assert(Seobeo_Worker_Pool_Wait(p_pool) == SEOBEO_WORKER_OK); + assert(atomic_load(&context.executed) == 2); + assert(atomic_load(&context.cleaned) == 2); + Seobeo_Worker_Pool_Destroy(p_pool); +} + +static void test_pool_cancel_queue(void) +{ + Worker_Test_Context context = {0}; + Seobeo_Worker_Pool *p_pool = + Seobeo_Worker_Pool_Create(1, 4); + assert(p_pool); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + blocking_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + wait_for_value(&context.entered, 1); + assert(Seobeo_Worker_Pool_Submit( + p_pool, + count_task, + &context, + cleanup_task) == SEOBEO_WORKER_OK); + Recursive_Cleanup_Context recursive = { + .p_pool = p_pool, + .wait_result = SEOBEO_WORKER_OK, + .shutdown_result = SEOBEO_WORKER_OK, + }; + assert(Seobeo_Worker_Pool_Submit( + p_pool, + no_op_task, + &recursive, + recursive_pool_cleanup) == SEOBEO_WORKER_OK); + + atomic_store(&context.hold_cleanup, 1); + Pool_Shutdown_Context shutdown = { + .p_pool = p_pool, + .result = SEOBEO_WORKER_THREAD_ERROR, + }; + Seobeo_Thread *p_shutdown = Seobeo_Thread_Start( + shutdown_pool_task, + &shutdown, + NULL); + assert(p_shutdown); + wait_for_value(&context.cleanup_entered, 1); + + Pool_Wait_Context wait = { + .p_pool = p_pool, + }; + Seobeo_Thread *p_wait = Seobeo_Thread_Start( + wait_pool_task, + &wait, + NULL); + assert(p_wait); + usleep(20000); + assert(atomic_load(&wait.returned) == 0); + + atomic_store(&context.release, 1); + assert(Seobeo_Thread_Join(p_shutdown) == SEOBEO_WORKER_OK); + assert(Seobeo_Thread_Join(p_wait) == SEOBEO_WORKER_OK); + assert(shutdown.result == SEOBEO_WORKER_OK); + assert(atomic_load(&context.executed) == 1); + assert(atomic_load(&context.cleaned) == 2); + assert(atomic_load(&recursive.cleaned) == 1); + assert(recursive.wait_result == SEOBEO_WORKER_INVALID_ARGUMENT); + assert(recursive.shutdown_result == SEOBEO_WORKER_INVALID_ARGUMENT); + Seobeo_Worker_Pool_Destroy(p_pool); +} + +int main(void) +{ + assert(Seobeo_Thread_Current_Id() != 0); + assert(Seobeo_Thread_Start(NULL, NULL, NULL) == NULL); + assert(Seobeo_Thread_Start_Detached( + NULL, + NULL, + NULL) == SEOBEO_WORKER_INVALID_ARGUMENT); + assert(Seobeo_Worker_Pool_Create(0, 1) == NULL); + assert(Seobeo_Worker_Pool_Create(1, 0) == NULL); + + test_joinable_thread(); + test_detached_thread(); + test_pool_drain(); + test_pool_queue_limit(); + test_pool_cancel_queue(); + return 0; +}