lardon3d/tests/test_sequential_task.c

726 lines
22 KiB
C

#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <lardon3d/task.h>
#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;
}