#include #include #include #include #include #include #include #include #include "resource_snapshot_test_utils.h" #define CHECK(condition) \ do { \ if (!(condition)) { \ (void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \ return false; \ } \ } while (0) typedef struct { Lardon3DResourceGovernor *governor; Lardon3DResourceReservation *initial_reservation; unsigned int expected_breaks; unsigned int breaks_done; size_t batch_sizes[8]; Lardon3DTaskExecutionContract initial_contract; bool got_initial_contract; } SeqContext; typedef struct { Lardon3DResourceGovernor *governor; pthread_mutex_t sync_mutex; pthread_cond_t sync_cond; bool in_break; } WaitRetryContext; typedef struct { Lardon3DResourceGovernor *governor; } RejectContext; typedef struct { Lardon3DResourceGovernor *governor; pthread_mutex_t sync_mutex; pthread_cond_t sync_cond; bool about_to_break; bool cancel_ready; } CancelContext; typedef struct { Lardon3DTask *task; Lardon3DResourceGovernor *governor; Lardon3DResourceReservation *reservation; } StartContext; static void short_pause(long nanoseconds) { struct timespec duration = {.tv_sec = 0, .tv_nsec = nanoseconds}; (void)nanosleep(&duration, NULL); } static bool seq_callback(Lardon3DTask *task, void *userdata) { SeqContext *ctx = userdata; if (!lardon3d_task_execution_contract(task, &ctx->initial_contract)) { return false; } ctx->got_initial_contract = true; Lardon3DResourceReservation *previous = ctx->initial_reservation; for (unsigned int index = 0; index < ctx->expected_breaks; ++index) { if (!lardon3d_task_checkpoint(task)) { return false; } /* Vérifier que la réservation précédente est encore valide AVANT le break */ if (previous && !lardon3d_resource_governor_reservation_is_valid( ctx->governor, previous )) { return false; } Lardon3DResourceReservation *reservation = NULL; Lardon3DTaskExecutionContract contract; if (!lardon3d_task_sequence_break( task, ctx->governor, &reservation, &contract )) { return false; } if (!lardon3d_resource_governor_reservation_is_valid( ctx->governor, reservation )) { return false; } ctx->batch_sizes[index] = contract.batch_size; previous = reservation; ++ctx->breaks_done; unsigned int progress = (unsigned int)( ((index + 1) * 100) / ctx->expected_breaks ); if (!lardon3d_task_set_progress(task, progress, "Séquence.")) { return false; } short_pause(2000000); } return true; } static bool wait_retry_callback(Lardon3DTask *task, void *userdata) { WaitRetryContext *ctx = userdata; (void)pthread_mutex_lock(&ctx->sync_mutex); ctx->in_break = true; (void)pthread_cond_broadcast(&ctx->sync_cond); (void)pthread_mutex_unlock(&ctx->sync_mutex); Lardon3DResourceReservation *reservation = NULL; Lardon3DTaskExecutionContract contract; return lardon3d_task_sequence_break( task, ctx->governor, &reservation, &contract ); } static bool reject_callback(Lardon3DTask *task, void *userdata) { RejectContext *ctx = userdata; Lardon3DResourceReservation *reservation = NULL; Lardon3DTaskExecutionContract contract; return lardon3d_task_sequence_break( task, ctx->governor, &reservation, &contract ); } static bool cancel_callback(Lardon3DTask *task, void *userdata) { CancelContext *ctx = userdata; if (!lardon3d_task_checkpoint(task)) { return false; } (void)pthread_mutex_lock(&ctx->sync_mutex); ctx->about_to_break = true; (void)pthread_cond_broadcast(&ctx->sync_cond); while (!ctx->cancel_ready) { (void)pthread_cond_wait(&ctx->sync_cond, &ctx->sync_mutex); } (void)pthread_mutex_unlock(&ctx->sync_mutex); Lardon3DResourceReservation *reservation = NULL; Lardon3DTaskExecutionContract contract; return lardon3d_task_sequence_break( task, ctx->governor, &reservation, &contract ); } static void * start_task(void *context) { StartContext *start = context; return (void *)(uintptr_t)(lardon3d_task_start( start->task, start->governor, start->reservation ) ? 1 : 0); } static bool wait_for_state(Lardon3DTask *task, Lardon3DTaskState expected) { for (size_t attempt = 0; attempt < 5000; ++attempt) { Lardon3DTaskSnapshot snapshot; if (!lardon3d_task_snapshot(task, &snapshot)) { return false; } if (snapshot.state == expected) { return true; } short_pause(1000000); } return false; } static bool run_sequential_test(void) { const Lardon3DResourceEstimate estimate = { .memory_bytes_per_item = 1000, .minimum_batch_size = 1, .maximum_batch_size = 1000, .desired_cpu_threads = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 100.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = 20000, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &estimate, &decision, &reservation )); CHECK(decision.kind == LARDON3D_RESOURCE_START || decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH); CHECK(decision.batch_size == 20); SeqContext ctx = { .governor = governor, .initial_reservation = reservation, .expected_breaks = 3, }; Lardon3DTask *task = lardon3d_task_create( "Séquences", &estimate, seq_callback, &ctx ); CHECK(task); StartContext start = {task, governor, reservation}; pthread_t thread; CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); CHECK(wait_for_state(task, TASK_RUNNING)); CHECK(lardon3d_task_join(task)); void *thread_result; CHECK(pthread_join(thread, &thread_result) == 0); CHECK((uintptr_t)thread_result == 1); CHECK(ctx.got_initial_contract); CHECK(ctx.breaks_done == 3); CHECK(ctx.initial_contract.batch_size == 20); CHECK(ctx.batch_sizes[0] == 1000); CHECK(ctx.batch_sizes[1] == 1000); CHECK(ctx.batch_sizes[2] == 1000); CHECK(ctx.batch_sizes[0] != ctx.initial_contract.batch_size); CHECK(lardon3d_task_sequence_count(task) == 3); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_COMPLETED); CHECK(snapshot.progress == 100); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); lardon3d_resource_governor_destroy(governor); return true; } static bool run_wait_retry_test(void) { const Lardon3DResourceEstimate estimate = { .minimum_batch_size = 1, .maximum_batch_size = 100, .desired_cpu_threads = 1, .desired_io_slots = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 0.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = UINT64_MAX, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &estimate, &decision, &reservation )); CHECK(reservation); WaitRetryContext ctx = {.governor = governor}; CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0); CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0); Lardon3DTask *task = lardon3d_task_create( "Attente puis reprise", &estimate, wait_retry_callback, &ctx ); CHECK(task); StartContext start = {task, governor, reservation}; pthread_t thread; CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); (void)pthread_mutex_lock(&ctx.sync_mutex); while (!ctx.in_break) { (void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex); } (void)pthread_mutex_unlock(&ctx.sync_mutex); /* Tant que la politique reste bloquante, le gouverneur répond WAIT : * la tâche doit rester en cours et ne jamais passer en échec. */ Lardon3DTaskSnapshot snapshot; for (size_t attempt = 0; attempt < 50; ++attempt) { CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_RUNNING); short_pause(1000000); } CHECK(lardon3d_task_sequence_count(task) == 0); /* Lever la contrainte : la prochaine tentative doit réussir. */ Lardon3DResourcePolicy allowed = policy; allowed.maximum_io_pressure_avg10 = 100.0; CHECK(lardon3d_resource_governor_set_policy(governor, &allowed)); CHECK(lardon3d_task_join(task)); void *thread_result; CHECK(pthread_join(thread, &thread_result) == 0); CHECK((uintptr_t)thread_result == 1); CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_COMPLETED); CHECK(lardon3d_task_sequence_count(task) == 1); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); (void)pthread_cond_destroy(&ctx.sync_cond); (void)pthread_mutex_destroy(&ctx.sync_mutex); lardon3d_resource_governor_destroy(governor); return true; } static bool run_cancel_during_wait_test(void) { const Lardon3DResourceEstimate estimate = { .minimum_batch_size = 1, .maximum_batch_size = 100, .desired_cpu_threads = 1, .desired_io_slots = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 0.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = UINT64_MAX, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &estimate, &decision, &reservation )); CHECK(reservation); WaitRetryContext ctx = {.governor = governor}; CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0); CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0); Lardon3DTask *task = lardon3d_task_create( "Annulation pendant attente", &estimate, wait_retry_callback, &ctx ); CHECK(task); StartContext start = {task, governor, reservation}; pthread_t thread; CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); (void)pthread_mutex_lock(&ctx.sync_mutex); while (!ctx.in_break) { (void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex); } (void)pthread_mutex_unlock(&ctx.sync_mutex); /* Annuler pendant que la tâche attend les ressources. */ lardon3d_task_request_cancel(task); CHECK(lardon3d_task_join(task)); CHECK(pthread_join(thread, NULL) == 0); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_CANCELLED); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); (void)pthread_cond_destroy(&ctx.sync_cond); (void)pthread_mutex_destroy(&ctx.sync_mutex); lardon3d_resource_governor_destroy(governor); return true; } static bool run_pause_during_wait_test(void) { const Lardon3DResourceEstimate estimate = { .minimum_batch_size = 1, .maximum_batch_size = 100, .desired_cpu_threads = 1, .desired_io_slots = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 0.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = UINT64_MAX, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &estimate, &decision, &reservation )); CHECK(reservation); WaitRetryContext ctx = {.governor = governor}; CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0); CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0); Lardon3DTask *task = lardon3d_task_create( "Pause pendant attente", &estimate, wait_retry_callback, &ctx ); CHECK(task); StartContext start = {task, governor, reservation}; pthread_t thread; CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); (void)pthread_mutex_lock(&ctx.sync_mutex); while (!ctx.in_break) { (void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex); } (void)pthread_mutex_unlock(&ctx.sync_mutex); /* Mettre en pause pendant l'attente, puis reprendre. */ CHECK(lardon3d_task_pause(task)); CHECK(wait_for_state(task, TASK_PAUSED)); CHECK(lardon3d_task_resume(task)); CHECK(wait_for_state(task, TASK_RUNNING)); /* Lever la contrainte : la tâche doit reprendre puis réussir. */ Lardon3DResourcePolicy allowed = policy; allowed.maximum_io_pressure_avg10 = 100.0; CHECK(lardon3d_resource_governor_set_policy(governor, &allowed)); CHECK(lardon3d_task_join(task)); CHECK(pthread_join(thread, NULL) == 0); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_COMPLETED); CHECK(lardon3d_task_sequence_count(task) == 1); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); (void)pthread_cond_destroy(&ctx.sync_cond); (void)pthread_mutex_destroy(&ctx.sync_mutex); lardon3d_resource_governor_destroy(governor); return true; } static bool run_reject_test(void) { const Lardon3DResourceEstimate valid_estimate = { .minimum_batch_size = 1, .maximum_batch_size = 1, .desired_cpu_threads = 1, }; const Lardon3DResourceEstimate reject_estimate = { .memory_fixed_bytes = UINT64_MAX, .memory_bytes_per_item = 1, .minimum_batch_size = 1, .maximum_batch_size = 1, .desired_cpu_threads = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 100.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = UINT64_MAX, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &valid_estimate, &decision, &reservation )); CHECK(reservation); RejectContext ctx = {.governor = governor}; Lardon3DTask *task = lardon3d_task_create( "Refus gouverneur", &reject_estimate, reject_callback, &ctx ); CHECK(task); CHECK(lardon3d_task_start(task, governor, reservation)); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_FAILED); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); lardon3d_resource_governor_destroy(governor); return true; } static bool run_invalid_break_test(void) { const Lardon3DResourceEstimate estimate = { .minimum_batch_size = 1, .maximum_batch_size = 1, .desired_cpu_threads = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 4, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 100.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DTask *task = lardon3d_task_create( "Sans réservation", &estimate, wait_retry_callback, NULL ); CHECK(task); Lardon3DResourceReservation *reservation = NULL; Lardon3DTaskExecutionContract contract; CHECK(!lardon3d_task_sequence_break(task, governor, &reservation, &contract)); CHECK(!lardon3d_task_sequence_break(NULL, governor, &reservation, &contract)); CHECK(!lardon3d_task_sequence_break(task, NULL, &reservation, &contract)); CHECK(!lardon3d_task_sequence_break(task, governor, NULL, &contract)); CHECK(!lardon3d_task_sequence_break(task, governor, &reservation, NULL)); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_PENDING); lardon3d_task_destroy(task); lardon3d_resource_governor_destroy(governor); return true; } static bool run_cancel_test(void) { const Lardon3DResourceEstimate estimate = { .minimum_batch_size = 1, .maximum_batch_size = 100, .desired_cpu_threads = 1, }; Lardon3DHardwareProfile profile = { .logical_cpu_count = 16, .page_size_bytes = 4096, .memory_total_bytes = UINT64_MAX, .cpu_architecture = "test", }; Lardon3DResourcePolicy policy = { .maximum_cpu_load_ratio = 1.0, .maximum_io_pressure_avg10 = 100.0, .io_slot_capacity = 1, }; Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( &profile, &policy ); CHECK(governor); Lardon3DResourceSnapshot resource_snapshot = { .memory_available_bytes = UINT64_MAX, .cpu_load_1m = 0.0, }; Lardon3DResourceDecision decision; Lardon3DResourceReservation *reservation; CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot)); CHECK(lardon3d_resource_governor_reserve( governor, &resource_snapshot, &estimate, &decision, &reservation )); CancelContext ctx = {.governor = governor}; CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0); CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0); Lardon3DTask *task = lardon3d_task_create( "Annulation", &estimate, cancel_callback, &ctx ); CHECK(task); StartContext start = {task, governor, reservation}; pthread_t thread; CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); (void)pthread_mutex_lock(&ctx.sync_mutex); while (!ctx.about_to_break) { (void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex); } (void)pthread_mutex_unlock(&ctx.sync_mutex); lardon3d_task_request_cancel(task); (void)pthread_mutex_lock(&ctx.sync_mutex); ctx.cancel_ready = true; (void)pthread_cond_broadcast(&ctx.sync_cond); (void)pthread_mutex_unlock(&ctx.sync_mutex); CHECK(lardon3d_task_join(task)); CHECK(pthread_join(thread, NULL) == 0); Lardon3DTaskSnapshot snapshot; CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_CANCELLED); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); lardon3d_task_destroy(task); (void)pthread_cond_destroy(&ctx.sync_cond); (void)pthread_mutex_destroy(&ctx.sync_mutex); lardon3d_resource_governor_destroy(governor); return true; } int main(void) { if (!run_sequential_test()) { return EXIT_FAILURE; } if (!run_wait_retry_test()) { return EXIT_FAILURE; } if (!run_cancel_test()) { return EXIT_FAILURE; } if (!run_cancel_during_wait_test()) { return EXIT_FAILURE; } if (!run_pause_during_wait_test()) { return EXIT_FAILURE; } if (!run_reject_test()) { return EXIT_FAILURE; } if (!run_invalid_break_test()) { return EXIT_FAILURE; } return EXIT_SUCCESS; }