feat(governor): add thread-safe wait for reservation changes
This commit is contained in:
parent
a986ad0add
commit
a716ef16bf
3 changed files with 279 additions and 1 deletions
|
|
@ -176,6 +176,14 @@ bool lardon3d_resource_governor_availability(
|
||||||
const Lardon3DResourceSnapshot *snapshot,
|
const Lardon3DResourceSnapshot *snapshot,
|
||||||
Lardon3DResourceAvailability *availability
|
Lardon3DResourceAvailability *availability
|
||||||
);
|
);
|
||||||
|
uint64_t lardon3d_resource_governor_generation(
|
||||||
|
Lardon3DResourceGovernor *governor
|
||||||
|
);
|
||||||
|
bool lardon3d_resource_governor_wait_for_change(
|
||||||
|
Lardon3DResourceGovernor *governor,
|
||||||
|
uint64_t observed_generation,
|
||||||
|
uint64_t timeout_ns
|
||||||
|
);
|
||||||
const char *lardon3d_resource_decision_name(
|
const char *lardon3d_resource_decision_name(
|
||||||
Lardon3DResourceDecisionKind kind
|
Lardon3DResourceDecisionKind kind
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <errno.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -13,6 +14,8 @@ struct Lardon3DResourceReservation {
|
||||||
|
|
||||||
struct Lardon3DResourceGovernor {
|
struct Lardon3DResourceGovernor {
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
uint64_t generation;
|
||||||
Lardon3DHardwareProfile profile;
|
Lardon3DHardwareProfile profile;
|
||||||
Lardon3DResourcePolicy policy;
|
Lardon3DResourcePolicy policy;
|
||||||
uint64_t next_reservation_id;
|
uint64_t next_reservation_id;
|
||||||
|
|
@ -203,6 +206,26 @@ lardon3d_resource_governor_create(
|
||||||
free(governor);
|
free(governor);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
pthread_condattr_t attr;
|
||||||
|
if (pthread_condattr_init(&attr) != 0) {
|
||||||
|
(void)pthread_mutex_destroy(&governor->mutex);
|
||||||
|
free(governor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) != 0) {
|
||||||
|
(void)pthread_condattr_destroy(&attr);
|
||||||
|
(void)pthread_mutex_destroy(&governor->mutex);
|
||||||
|
free(governor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (pthread_cond_init(&governor->cond, &attr) != 0) {
|
||||||
|
(void)pthread_condattr_destroy(&attr);
|
||||||
|
(void)pthread_mutex_destroy(&governor->mutex);
|
||||||
|
free(governor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
(void)pthread_condattr_destroy(&attr);
|
||||||
|
governor->generation = 0;
|
||||||
governor->profile = *profile;
|
governor->profile = *profile;
|
||||||
governor->policy = *policy;
|
governor->policy = *policy;
|
||||||
governor->next_reservation_id = 1;
|
governor->next_reservation_id = 1;
|
||||||
|
|
@ -227,6 +250,7 @@ lardon3d_resource_governor_destroy(Lardon3DResourceGovernor *governor)
|
||||||
free(reservation);
|
free(reservation);
|
||||||
reservation = next;
|
reservation = next;
|
||||||
}
|
}
|
||||||
|
(void)pthread_cond_destroy(&governor->cond);
|
||||||
(void)pthread_mutex_destroy(&governor->mutex);
|
(void)pthread_mutex_destroy(&governor->mutex);
|
||||||
free(governor);
|
free(governor);
|
||||||
}
|
}
|
||||||
|
|
@ -347,6 +371,62 @@ lardon3d_resource_governor_availability(
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
lardon3d_resource_governor_generation(
|
||||||
|
Lardon3DResourceGovernor *governor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!governor) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_lock(&governor->mutex);
|
||||||
|
uint64_t generation = governor->generation;
|
||||||
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
|
return generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lardon3d_resource_governor_wait_for_change(
|
||||||
|
Lardon3DResourceGovernor *governor,
|
||||||
|
uint64_t observed_generation,
|
||||||
|
uint64_t timeout_ns
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!governor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct timespec deadline;
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
deadline.tv_sec += (time_t)(timeout_ns / 1000000000ULL);
|
||||||
|
uint64_t remainder = timeout_ns % 1000000000ULL;
|
||||||
|
deadline.tv_nsec += (long)remainder;
|
||||||
|
if (deadline.tv_nsec >= 1000000000L) {
|
||||||
|
deadline.tv_sec += 1;
|
||||||
|
deadline.tv_nsec -= 1000000000L;
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_lock(&governor->mutex);
|
||||||
|
bool changed = false;
|
||||||
|
while (governor->generation == observed_generation) {
|
||||||
|
int result = pthread_cond_timedwait(
|
||||||
|
&governor->cond,
|
||||||
|
&governor->mutex,
|
||||||
|
&deadline
|
||||||
|
);
|
||||||
|
if (result == ETIMEDOUT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result != 0) {
|
||||||
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = governor->generation != observed_generation;
|
||||||
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
evaluate_locked(
|
evaluate_locked(
|
||||||
Lardon3DResourceGovernor *governor,
|
Lardon3DResourceGovernor *governor,
|
||||||
|
|
@ -554,6 +634,8 @@ lardon3d_resource_governor_reserve(
|
||||||
governor->io_slots_reserved += decision->io_slots;
|
governor->io_slots_reserved += decision->io_slots;
|
||||||
++governor->active_count;
|
++governor->active_count;
|
||||||
*reservation = created;
|
*reservation = created;
|
||||||
|
++governor->generation;
|
||||||
|
(void)pthread_cond_broadcast(&governor->cond);
|
||||||
(void)pthread_mutex_unlock(&governor->mutex);
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -635,6 +717,8 @@ lardon3d_resource_governor_release(
|
||||||
current->information.state = LARDON3D_RESERVATION_RELEASED;
|
current->information.state = LARDON3D_RESERVATION_RELEASED;
|
||||||
current->next = governor->released;
|
current->next = governor->released;
|
||||||
governor->released = current;
|
governor->released = current;
|
||||||
|
++governor->generation;
|
||||||
|
(void)pthread_cond_broadcast(&governor->cond);
|
||||||
(void)pthread_mutex_unlock(&governor->mutex);
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,26 @@ typedef struct {
|
||||||
atomic_bool *failed;
|
atomic_bool *failed;
|
||||||
} ThreadContext;
|
} ThreadContext;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Lardon3DResourceGovernor *governor;
|
||||||
|
uint64_t observed_generation;
|
||||||
|
uint64_t timeout_ns;
|
||||||
|
atomic_bool *result;
|
||||||
|
} WaitContext;
|
||||||
|
|
||||||
|
static void *
|
||||||
|
wait_for_change_thread(void *argument)
|
||||||
|
{
|
||||||
|
WaitContext *context = argument;
|
||||||
|
bool changed = lardon3d_resource_governor_wait_for_change(
|
||||||
|
context->governor,
|
||||||
|
context->observed_generation,
|
||||||
|
context->timeout_ns
|
||||||
|
);
|
||||||
|
atomic_store(context->result, changed);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void *
|
static void *
|
||||||
decide_repeatedly(void *argument)
|
decide_repeatedly(void *argument)
|
||||||
{
|
{
|
||||||
|
|
@ -207,8 +227,174 @@ run_test(void)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
run_generation_test(void)
|
||||||
|
{
|
||||||
|
Lardon3DHardwareProfile profile = {
|
||||||
|
.logical_cpu_count = 16,
|
||||||
|
.page_size_bytes = 4096,
|
||||||
|
.memory_total_bytes = GIBIBYTES(16),
|
||||||
|
.cpu_architecture = "test",
|
||||||
|
};
|
||||||
|
Lardon3DResourcePolicy policy = {
|
||||||
|
.system_memory_reserve_bytes = GIBIBYTES(2),
|
||||||
|
.gpu_memory_reserve_bytes = 0,
|
||||||
|
.system_cpu_reserve = 1,
|
||||||
|
.maximum_cpu_load_ratio = 0.90,
|
||||||
|
.maximum_io_pressure_avg10 = 80.0,
|
||||||
|
.io_slot_capacity = 8,
|
||||||
|
};
|
||||||
|
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
|
||||||
|
&profile,
|
||||||
|
&policy
|
||||||
|
);
|
||||||
|
CHECK(governor);
|
||||||
|
|
||||||
|
/* Test 1 : génération initiale = 0. */
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 0);
|
||||||
|
|
||||||
|
Lardon3DResourceSnapshot snapshot = {
|
||||||
|
.memory_available_bytes = GIBIBYTES(10),
|
||||||
|
.cpu_load_1m = 2.0,
|
||||||
|
};
|
||||||
|
Lardon3DResourceEstimate estimate = {
|
||||||
|
.memory_bytes_per_item = GIBIBYTES(1),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.maximum_batch_size = 8,
|
||||||
|
.desired_cpu_threads = 4,
|
||||||
|
.task_class = LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
};
|
||||||
|
Lardon3DResourceDecision decision;
|
||||||
|
Lardon3DResourceReservation *reservation = NULL;
|
||||||
|
|
||||||
|
/* Test 2 : WAIT ne change pas la génération. */
|
||||||
|
snapshot.cpu_load_1m = 15.0;
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
|
||||||
|
CHECK(reservation == NULL);
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 0);
|
||||||
|
snapshot.cpu_load_1m = 2.0;
|
||||||
|
|
||||||
|
/* Test 3 : REJECT ne change pas la génération. */
|
||||||
|
estimate.memory_bytes_per_item = GIBIBYTES(8);
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_REJECT);
|
||||||
|
CHECK(reservation == NULL);
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 0);
|
||||||
|
estimate.memory_bytes_per_item = GIBIBYTES(1);
|
||||||
|
|
||||||
|
/* Test 4 : création d'une réservation change la génération (0 -> 1). */
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START
|
||||||
|
|| decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
|
||||||
|
CHECK(reservation != NULL);
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 1);
|
||||||
|
|
||||||
|
/* Test 5 : libération change la génération (1 -> 2). */
|
||||||
|
CHECK(lardon3d_resource_governor_release(governor, reservation));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 2);
|
||||||
|
|
||||||
|
/* Test 6 : double libération refusée ne change pas la génération. */
|
||||||
|
CHECK(!lardon3d_resource_governor_release(governor, reservation));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == 2);
|
||||||
|
|
||||||
|
/* Test 7 : un thread attendant est réveillé après une libération. */
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(reservation != NULL);
|
||||||
|
uint64_t generation = lardon3d_resource_governor_generation(governor);
|
||||||
|
atomic_bool wait_result = false;
|
||||||
|
WaitContext wait_context = {
|
||||||
|
.governor = governor,
|
||||||
|
.observed_generation = generation,
|
||||||
|
.timeout_ns = 5000000000ULL,
|
||||||
|
.result = &wait_result,
|
||||||
|
};
|
||||||
|
pthread_t waiter;
|
||||||
|
CHECK(pthread_create(&waiter, NULL, wait_for_change_thread, &wait_context) == 0);
|
||||||
|
CHECK(lardon3d_resource_governor_release(governor, reservation));
|
||||||
|
CHECK(pthread_join(waiter, NULL) == 0);
|
||||||
|
CHECK(atomic_load(&wait_result) == true);
|
||||||
|
|
||||||
|
/* Test 8 : l'attente expire proprement (timeout court). */
|
||||||
|
generation = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(!lardon3d_resource_governor_wait_for_change(
|
||||||
|
governor,
|
||||||
|
generation,
|
||||||
|
1000000ULL
|
||||||
|
));
|
||||||
|
|
||||||
|
/* Test 9 : une décision WAIT ne modifie pas la génération et
|
||||||
|
* wait_for_change() expire sans faux changement. */
|
||||||
|
generation = lardon3d_resource_governor_generation(governor);
|
||||||
|
snapshot.cpu_load_1m = 15.0;
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
|
||||||
|
CHECK(reservation == NULL);
|
||||||
|
snapshot.cpu_load_1m = 2.0;
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == generation);
|
||||||
|
CHECK(!lardon3d_resource_governor_wait_for_change(
|
||||||
|
governor,
|
||||||
|
generation,
|
||||||
|
1000000ULL
|
||||||
|
));
|
||||||
|
|
||||||
|
/* Test 10 : plusieurs attentes successives fonctionnent. */
|
||||||
|
for (size_t index = 0; index < 3; ++index) {
|
||||||
|
CHECK(lardon3d_resource_governor_reserve(
|
||||||
|
governor,
|
||||||
|
&snapshot,
|
||||||
|
&estimate,
|
||||||
|
&decision,
|
||||||
|
&reservation
|
||||||
|
));
|
||||||
|
CHECK(reservation != NULL);
|
||||||
|
generation = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(lardon3d_resource_governor_release(governor, reservation));
|
||||||
|
CHECK(lardon3d_resource_governor_wait_for_change(
|
||||||
|
governor,
|
||||||
|
generation,
|
||||||
|
5000000000ULL
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
lardon3d_resource_governor_destroy(governor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void)
|
main(void)
|
||||||
{
|
{
|
||||||
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
|
return (run_test() && run_generation_test())
|
||||||
|
? EXIT_SUCCESS
|
||||||
|
: EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue