1503 lines
50 KiB
C
1503 lines
50 KiB
C
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <lardon3d/task.h>
|
|
|
|
#include "task_internal.h"
|
|
|
|
struct Lardon3DTask {
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t condition;
|
|
uint64_t id;
|
|
char name[LARDON3D_TASK_NAME_CAPACITY];
|
|
unsigned int progress;
|
|
bool durable_progress_known;
|
|
uint64_t durable_completed;
|
|
uint64_t durable_total;
|
|
Lardon3DTaskState state;
|
|
char message[LARDON3D_TASK_MESSAGE_CAPACITY];
|
|
struct timespec started_at;
|
|
struct timespec finished_at;
|
|
Lardon3DTaskCallback callback;
|
|
void *userdata;
|
|
Lardon3DTaskUserdataDestroy userdata_destroy;
|
|
Lardon3DTaskFinishedCallback finished_callback;
|
|
void *finished_userdata;
|
|
bool finished_notified;
|
|
bool finished_callback_running;
|
|
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
|
uint32_t task_kind_version;
|
|
Lardon3DResourceEstimate estimate;
|
|
Lardon3DTaskCapabilityEnvelope capability_envelope;
|
|
Lardon3DResourceCapabilitySelection pending_selection;
|
|
Lardon3DResourceCapabilitySelection selected_capability;
|
|
Lardon3DResourceReservation *pending_reservation;
|
|
bool has_selected_capability;
|
|
Lardon3DTaskExecutionContract contract;
|
|
bool has_contract;
|
|
bool pause_requested;
|
|
bool cancel_requested;
|
|
bool executing;
|
|
Lardon3DResourceGovernor *governor;
|
|
Lardon3DResourceReservation *current_reservation;
|
|
unsigned int sequence_count;
|
|
/* Matcher candidate-pair publication is strictly ascending within one
|
|
* Task. This private, non-persisted watermark makes item telemetry
|
|
* idempotent in-process without turning it into checkpoint state. */
|
|
uint64_t fallback_item_high_water;
|
|
bool fallback_items_saturated;
|
|
uint64_t local_ineligible_fallback_items;
|
|
uint64_t backend_failure_fallback_items;
|
|
uint64_t backend_other_fallback_items;
|
|
#ifdef LARDON3D_TASK_TESTING
|
|
bool test_force_sequence_association_mismatch;
|
|
unsigned int test_association_failure_releases;
|
|
#endif
|
|
};
|
|
|
|
static bool
|
|
is_terminal(Lardon3DTaskState state)
|
|
{
|
|
return state == TASK_CANCELLED || state == TASK_FAILED
|
|
|| state == TASK_COMPLETED;
|
|
}
|
|
|
|
static bool
|
|
valid_state(Lardon3DTaskState state)
|
|
{
|
|
return state >= TASK_PENDING && state <= TASK_COMPLETED;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_kind_is_valid(const char *task_kind)
|
|
{
|
|
if (!task_kind) {
|
|
return false;
|
|
}
|
|
size_t length = strnlen(task_kind, LARDON3D_TASK_KIND_CAPACITY);
|
|
if (length == 0 || length >= LARDON3D_TASK_KIND_CAPACITY) {
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < length; ++index) {
|
|
char character = task_kind[index];
|
|
if (!((character >= 'a' && character <= 'z')
|
|
|| (character >= '0' && character <= '9')
|
|
|| (index > 0 && (character == '.' || character == '_'
|
|
|| character == '-')))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static Lardon3DTaskState
|
|
recovery_state(Lardon3DTaskState state)
|
|
{
|
|
return state == TASK_RUNNING || state == TASK_PAUSED ? TASK_PENDING : state;
|
|
}
|
|
|
|
static void
|
|
copy_text(char *destination, size_t capacity, const char *text)
|
|
{
|
|
(void)snprintf(destination, capacity, "%s", text ? text : "");
|
|
}
|
|
|
|
static bool
|
|
kind_has_validated_cpu_range(const char *task_kind, uint32_t task_kind_version)
|
|
{
|
|
if (!task_kind || task_kind_version != 1) {
|
|
return false;
|
|
}
|
|
return strcmp(task_kind, "features.extract") == 0
|
|
|| strcmp(task_kind, "features.extract.batch") == 0
|
|
|| strcmp(task_kind, "features.extract.sift") == 0
|
|
|| strcmp(task_kind, "features.extract.rootsift") == 0
|
|
|| strcmp(task_kind, "visual_index.update") == 0
|
|
|| strcmp(task_kind, "candidate_pair.generate") == 0
|
|
|| strcmp(task_kind, "geometric_verifier.run") == 0;
|
|
}
|
|
|
|
static bool
|
|
kind_has_validated_batch_range(const char *task_kind, uint32_t version)
|
|
{
|
|
/* Candidate generation explicitly co-adapts its sequence window. GV keeps
|
|
* its 16-item preparation window while trialling CPU, because CPU2 with a
|
|
* one-item batch cannot exercise a second participant; Gate G may still
|
|
* reduce that bounded window for capacity or pressure. */
|
|
return task_kind && version == 1
|
|
&& (strcmp(task_kind, "candidate_pair.generate") == 0
|
|
|| strcmp(task_kind, "features.extract.batch") == 0);
|
|
}
|
|
|
|
static bool
|
|
kind_has_coupled_cpu_batch_range(const char *task_kind, uint32_t version)
|
|
{
|
|
/* Feature batch and Candidate obtain CPU parallelism only across
|
|
* independent items. A CPU2/batch1 trial is structurally incapable of
|
|
* measuring either path, so Governor feedback must trial the complete
|
|
* participant rung without changing scientific ordering or publication. */
|
|
return task_kind && version == 1
|
|
&& (strcmp(task_kind, "features.extract.batch") == 0
|
|
|| strcmp(task_kind, "candidate_pair.generate") == 0);
|
|
}
|
|
|
|
static void
|
|
finish_locked(
|
|
Lardon3DTask *task,
|
|
Lardon3DTaskState state,
|
|
const char *message
|
|
)
|
|
{
|
|
task->state = state;
|
|
if (state == TASK_COMPLETED) {
|
|
task->progress = 100;
|
|
}
|
|
if (message) {
|
|
copy_text(task->message, sizeof(task->message), message);
|
|
}
|
|
(void)clock_gettime(CLOCK_REALTIME, &task->finished_at);
|
|
task->executing = false;
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
}
|
|
|
|
static void
|
|
notify_finished(Lardon3DTask *task)
|
|
{
|
|
Lardon3DTaskFinishedCallback callback = NULL;
|
|
void *userdata = NULL;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (is_terminal(task->state) && !task->finished_notified) {
|
|
task->finished_notified = true;
|
|
callback = task->finished_callback;
|
|
userdata = task->finished_userdata;
|
|
task->finished_callback_running = callback != NULL;
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (callback) {
|
|
callback(task, userdata);
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->finished_callback_running = false;
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
}
|
|
}
|
|
|
|
Lardon3DTask *
|
|
lardon3d_task_create(
|
|
const char *name,
|
|
const Lardon3DResourceEstimate *estimate,
|
|
Lardon3DTaskCallback callback,
|
|
void *userdata
|
|
)
|
|
{
|
|
return lardon3d_task_create_typed(
|
|
name, estimate, NULL, 0, callback, userdata, NULL
|
|
);
|
|
}
|
|
|
|
Lardon3DTask *
|
|
lardon3d_task_create_typed(
|
|
const char *name,
|
|
const Lardon3DResourceEstimate *estimate,
|
|
const char *task_kind,
|
|
uint32_t task_kind_version,
|
|
Lardon3DTaskCallback callback,
|
|
void *userdata,
|
|
Lardon3DTaskUserdataDestroy userdata_destroy
|
|
)
|
|
{
|
|
bool typed = task_kind != NULL;
|
|
if (!name || !name[0] || !estimate || !callback
|
|
|| (typed && (!lardon3d_task_kind_is_valid(task_kind)
|
|
|| task_kind_version == 0))
|
|
|| (!typed && (task_kind_version != 0 || userdata_destroy))) {
|
|
return NULL;
|
|
}
|
|
Lardon3DTask *task = calloc(1, sizeof(*task));
|
|
if (!task) {
|
|
return NULL;
|
|
}
|
|
int written = snprintf(task->name, sizeof(task->name), "%s", name);
|
|
if (written < 0 || (size_t)written >= sizeof(task->name)
|
|
|| pthread_mutex_init(&task->mutex, NULL) != 0) {
|
|
free(task);
|
|
return NULL;
|
|
}
|
|
if (pthread_cond_init(&task->condition, NULL) != 0) {
|
|
(void)pthread_mutex_destroy(&task->mutex);
|
|
free(task);
|
|
return NULL;
|
|
}
|
|
task->state = TASK_PENDING;
|
|
task->callback = callback;
|
|
task->userdata = userdata;
|
|
task->userdata_destroy = userdata_destroy;
|
|
if (typed) {
|
|
(void)snprintf(task->task_kind, sizeof(task->task_kind), "%s", task_kind);
|
|
task->task_kind_version = task_kind_version;
|
|
}
|
|
task->estimate = *estimate;
|
|
/* Every Task starts with one honest capability identical to its canonical
|
|
* durable estimate. It remains fixed unless kind/version proves a bounded
|
|
* operational range or installs alternatives before admission; the
|
|
* durable estimate is never mutated. */
|
|
task->capability_envelope = (Lardon3DTaskCapabilityEnvelope) {
|
|
.count = 1,
|
|
.capabilities = {{
|
|
.estimate = *estimate,
|
|
.backend = LARDON3D_RESOURCE_BACKEND_FIXED,
|
|
.inflight_limit = 1,
|
|
/* A durable estimate is a fixed operational envelope unless its
|
|
* registered kind has proved that its callback consumes a CPU
|
|
* range without changing scientific identity. This private bit
|
|
* is reconstructed from kind/version and is never persisted. */
|
|
.cpu_reducible = typed
|
|
&& kind_has_validated_cpu_range(task_kind, task_kind_version),
|
|
.batch_adaptive = typed
|
|
&& kind_has_validated_batch_range(task_kind, task_kind_version),
|
|
.cpu_batch_coupled = typed
|
|
&& kind_has_coupled_cpu_batch_range(
|
|
task_kind, task_kind_version),
|
|
}},
|
|
};
|
|
copy_text(task->message, sizeof(task->message), "En attente.");
|
|
return task;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_set_capability_envelope(
|
|
Lardon3DTask *task,
|
|
const Lardon3DTaskCapabilityEnvelope *envelope
|
|
)
|
|
{
|
|
if (!task || !envelope || envelope->count == 0
|
|
|| envelope->count > LARDON3D_RESOURCE_CAPABILITY_MAX) {
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < envelope->count; ++index) {
|
|
const Lardon3DResourceEstimate *estimate =
|
|
&envelope->capabilities[index].estimate;
|
|
const Lardon3DTaskCapability *capability =
|
|
&envelope->capabilities[index];
|
|
size_t minimum_inflight = capability->minimum_inflight_limit != 0
|
|
? capability->minimum_inflight_limit : capability->inflight_limit;
|
|
if (estimate->minimum_batch_size == 0
|
|
|| estimate->maximum_batch_size < estimate->minimum_batch_size
|
|
|| estimate->desired_cpu_threads == 0
|
|
|| capability->inflight_limit == 0
|
|
|| minimum_inflight > capability->inflight_limit
|
|
|| (capability->sustained_gpu_batch_feedback
|
|
&& (!capability->batch_adaptive
|
|
|| capability->backend
|
|
!= LARDON3D_RESOURCE_BACKEND_ORB_VULKAN))
|
|
|| (capability->cpu_batch_coupled
|
|
&& (!capability->cpu_reducible
|
|
|| !capability->batch_adaptive))
|
|
|| (capability->inflight_adaptive
|
|
&& (capability->minimum_inflight_limit == 0
|
|
|| capability->gpu_memory_bytes_per_inflight == 0))
|
|
|| (capability->gpu_memory_bytes_per_inflight != 0
|
|
&& capability->inflight_limit
|
|
> UINT64_MAX / capability->gpu_memory_bytes_per_inflight)) {
|
|
return false;
|
|
}
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !task->executing && task->state == TASK_PENDING
|
|
&& !task->current_reservation && !task->pending_reservation;
|
|
if (accepted) {
|
|
/* Task owns this bounded copy for its lifetime. Hardware/backend
|
|
* availability remains Governor-owned and is checked at admission. */
|
|
task->capability_envelope = *envelope;
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_enable_known_capabilities(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
/* Recovery repeats the same private kind/version derivation used at fresh
|
|
* creation. Matcher and acquisition hooks may replace this one-capability
|
|
* default immediately afterward; the canonical durable estimate remains
|
|
* untouched in every case. */
|
|
if (task->capability_envelope.count == 1
|
|
&& task->capability_envelope.capabilities[0].backend
|
|
== LARDON3D_RESOURCE_BACKEND_FIXED) {
|
|
task->capability_envelope.capabilities[0].cpu_reducible =
|
|
kind_has_validated_cpu_range(
|
|
task->task_kind, task->task_kind_version);
|
|
task->capability_envelope.capabilities[0].batch_adaptive =
|
|
kind_has_validated_batch_range(
|
|
task->task_kind, task->task_kind_version);
|
|
task->capability_envelope.capabilities[0].cpu_batch_coupled =
|
|
kind_has_coupled_cpu_batch_range(
|
|
task->task_kind, task->task_kind_version);
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_reserve_available(
|
|
Lardon3DTask *task,
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceDecision *decision,
|
|
Lardon3DResourceReservation **reservation
|
|
)
|
|
{
|
|
if (!task || !governor || !decision || !reservation) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
Lardon3DTaskCapabilityEnvelope envelope = task->capability_envelope;
|
|
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
|
copy_text(task_kind, sizeof(task_kind), task->task_kind[0]
|
|
? task->task_kind : "task.untyped");
|
|
uint32_t task_kind_version = task->task_kind_version;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
|
|
Lardon3DResourceCapabilitySelection selection;
|
|
Lardon3DResourceReservation *created = NULL;
|
|
if (!lardon3d_resource_governor_internal_reserve_capability_available(
|
|
governor,
|
|
task_kind,
|
|
task_kind_version,
|
|
&envelope,
|
|
&selection,
|
|
&created
|
|
)) {
|
|
return false;
|
|
}
|
|
*decision = selection.decision;
|
|
*reservation = created;
|
|
if (created) {
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (task->pending_reservation) {
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
(void)lardon3d_resource_governor_release(governor, created);
|
|
*reservation = NULL;
|
|
return false;
|
|
}
|
|
task->pending_selection = selection;
|
|
task->pending_reservation = created;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_record_sequence_execution(
|
|
Lardon3DTask *task,
|
|
uint64_t wall_time_ns,
|
|
size_t items_completed,
|
|
Lardon3DResourceBackend actual_backend,
|
|
const char *backend_reason
|
|
)
|
|
{
|
|
return lardon3d_task_internal_record_sequence_execution_metrics(
|
|
task, wall_time_ns, items_completed, actual_backend, backend_reason,
|
|
NULL);
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_record_sequence_execution_metrics(
|
|
Lardon3DTask *task,
|
|
uint64_t wall_time_ns,
|
|
size_t items_completed,
|
|
Lardon3DResourceBackend actual_backend,
|
|
const char *backend_reason,
|
|
const Lardon3DResourceExecutionMetrics *metrics
|
|
)
|
|
{
|
|
if (!task || wall_time_ns == 0 || !backend_reason) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool available = task->executing && task->governor
|
|
&& task->has_selected_capability;
|
|
Lardon3DResourceGovernor *governor = task->governor;
|
|
Lardon3DResourceCapabilitySelection selection = task->selected_capability;
|
|
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
|
copy_text(task_kind, sizeof(task_kind), task->task_kind[0]
|
|
? task->task_kind : "task.untyped");
|
|
uint32_t task_kind_version = task->task_kind_version;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return available
|
|
&& lardon3d_resource_governor_internal_record_sequence_execution_metrics(
|
|
governor,
|
|
task_kind,
|
|
task_kind_version,
|
|
&selection,
|
|
wall_time_ns,
|
|
items_completed,
|
|
actual_backend,
|
|
backend_reason,
|
|
metrics
|
|
);
|
|
}
|
|
|
|
static void
|
|
increment_task_fallback_counter(uint64_t *counter, bool *saturated)
|
|
{
|
|
if (*counter == UINT64_MAX) {
|
|
*saturated = true;
|
|
} else {
|
|
++*counter;
|
|
}
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_record_fallback_item(
|
|
Lardon3DTask *task,
|
|
uint64_t candidate_pair_id,
|
|
Lardon3DResourceFallbackItemCause cause
|
|
)
|
|
{
|
|
if (!task || candidate_pair_id == 0
|
|
|| cause < LARDON3D_RESOURCE_FALLBACK_ITEM_LOCAL_INELIGIBLE
|
|
|| cause > LARDON3D_RESOURCE_FALLBACK_ITEM_OTHER) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool available = task->executing && task->governor
|
|
&& task->has_selected_capability
|
|
&& task->selected_capability.capability.backend
|
|
== LARDON3D_RESOURCE_BACKEND_ORB_VULKAN;
|
|
if (!available || candidate_pair_id <= task->fallback_item_high_water) {
|
|
bool duplicate = available
|
|
&& candidate_pair_id <= task->fallback_item_high_water;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return duplicate;
|
|
}
|
|
Lardon3DResourceGovernor *governor = task->governor;
|
|
Lardon3DResourceCapabilitySelection selection = task->selected_capability;
|
|
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
|
copy_text(task_kind, sizeof(task_kind), task->task_kind[0]
|
|
? task->task_kind : "task.untyped");
|
|
uint32_t task_kind_version = task->task_kind_version;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
|
|
if (!lardon3d_resource_governor_internal_record_fallback_items(
|
|
governor, task_kind, task_kind_version, &selection, cause, 1)) {
|
|
return false;
|
|
}
|
|
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
/* Queue owns the single callback, so no second recorder can pass the
|
|
* watermark concurrently. Retain the check to make the private operation
|
|
* idempotent even if a caller retries after a successful commit. */
|
|
if (candidate_pair_id > task->fallback_item_high_water) {
|
|
task->fallback_item_high_water = candidate_pair_id;
|
|
switch (cause) {
|
|
case LARDON3D_RESOURCE_FALLBACK_ITEM_LOCAL_INELIGIBLE:
|
|
increment_task_fallback_counter(
|
|
&task->local_ineligible_fallback_items,
|
|
&task->fallback_items_saturated);
|
|
break;
|
|
case LARDON3D_RESOURCE_FALLBACK_ITEM_BACKEND_FAILURE:
|
|
increment_task_fallback_counter(
|
|
&task->backend_failure_fallback_items,
|
|
&task->fallback_items_saturated);
|
|
break;
|
|
case LARDON3D_RESOURCE_FALLBACK_ITEM_OTHER:
|
|
increment_task_fallback_counter(
|
|
&task->backend_other_fallback_items,
|
|
&task->fallback_items_saturated);
|
|
break;
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_record_sequence(
|
|
Lardon3DTask *task,
|
|
uint64_t wall_time_ns,
|
|
size_t items_completed
|
|
)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
Lardon3DResourceBackend selected = task->has_selected_capability
|
|
? task->selected_capability.capability.backend
|
|
: LARDON3D_RESOURCE_BACKEND_FIXED;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return lardon3d_task_internal_record_sequence_execution(
|
|
task,
|
|
wall_time_ns,
|
|
items_completed,
|
|
selected,
|
|
"selected-backend-completed"
|
|
);
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_execution_selection(
|
|
const Lardon3DTask *task,
|
|
Lardon3DResourceCapabilitySelection *selection
|
|
)
|
|
{
|
|
if (!task || !selection) return false;
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
bool available = task->executing && task->has_selected_capability
|
|
&& task->has_contract;
|
|
if (available) {
|
|
*selection = task->selected_capability;
|
|
}
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return available;
|
|
}
|
|
|
|
#ifdef LARDON3D_TASK_TESTING
|
|
bool
|
|
lardon3d_task_internal_test_force_sequence_association_mismatch(
|
|
Lardon3DTask *task
|
|
)
|
|
{
|
|
if (!task) return false;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = task->executing && !is_terminal(task->state);
|
|
if (accepted) {
|
|
task->test_force_sequence_association_mismatch = true;
|
|
task->test_association_failure_releases = 0;
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_internal_test_has_reservation_ownership(Lardon3DTask *task)
|
|
{
|
|
if (!task) return false;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool owned = task->current_reservation || task->pending_reservation;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return owned;
|
|
}
|
|
|
|
unsigned int
|
|
lardon3d_task_internal_test_association_failure_releases(Lardon3DTask *task)
|
|
{
|
|
if (!task) return 0;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
unsigned int count = task->test_association_failure_releases;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
lardon3d_task_destroy(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (!is_terminal(task->state)) {
|
|
if (task->executing) {
|
|
task->cancel_requested = true;
|
|
copy_text(task->message, sizeof(task->message),
|
|
"Annulation demandée.");
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
} else {
|
|
/* Une tâche locale jamais soumise peut être abandonnée sans
|
|
* publier une fausse annulation métier. */
|
|
finish_locked(task, TASK_CANCELLED, "Tâche abandonnée.");
|
|
task->finished_notified = true;
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
(void)lardon3d_task_join(task);
|
|
(void)pthread_cond_destroy(&task->condition);
|
|
(void)pthread_mutex_destroy(&task->mutex);
|
|
if (task->userdata_destroy) {
|
|
task->userdata_destroy(task->userdata);
|
|
}
|
|
free(task);
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_start(
|
|
Lardon3DTask *task,
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceReservation *reservation
|
|
)
|
|
{
|
|
/* Start assumes admission/contract was already decided by the queue/ governor.
|
|
* Task only installs the provided active reservation and owns execution state.
|
|
*/
|
|
Lardon3DResourceReservationInfo information;
|
|
if (!task || !lardon3d_resource_reservation_get_active(
|
|
governor,
|
|
reservation,
|
|
&information
|
|
)) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (task->executing || is_terminal(task->state)
|
|
|| (task->pending_reservation
|
|
&& task->pending_reservation != reservation)) {
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
task->executing = true;
|
|
task->governor = governor;
|
|
task->current_reservation = (Lardon3DResourceReservation *)reservation;
|
|
if (task->pending_reservation == reservation) {
|
|
task->selected_capability = task->pending_selection;
|
|
task->has_selected_capability = true;
|
|
task->pending_reservation = NULL;
|
|
} else {
|
|
/* Direct public callers remain compatible. Their reservation is an
|
|
* immutable fixed selection for this sequence. They did not ask the
|
|
* private Governor capability chooser, so an adaptive envelope must
|
|
* use its durable/minimum inflight rather than advertise an
|
|
* unreserved operational maximum. Only Queue-owned admission may
|
|
* install a higher adaptive depth. */
|
|
const Lardon3DTaskCapability *direct_capability =
|
|
&task->capability_envelope.capabilities[0];
|
|
size_t direct_inflight = direct_capability->inflight_adaptive
|
|
? direct_capability->minimum_inflight_limit
|
|
: direct_capability->inflight_limit;
|
|
task->selected_capability = (Lardon3DResourceCapabilitySelection) {
|
|
.capability = *direct_capability,
|
|
.reservation_estimate = direct_capability->estimate,
|
|
.decision = {
|
|
.kind = LARDON3D_RESOURCE_START,
|
|
.batch_size = information.batch_size,
|
|
.cpu_threads = information.cpu_threads,
|
|
.gpu_slots = information.gpu_slots,
|
|
.io_slots = information.io_slots,
|
|
},
|
|
.inflight_limit = direct_inflight,
|
|
.pressure = lardon3d_resource_governor_pressure(governor),
|
|
};
|
|
task->has_selected_capability = true;
|
|
}
|
|
task->contract = (Lardon3DTaskExecutionContract) {
|
|
.batch_size = information.batch_size,
|
|
.memory_bytes = information.memory_bytes,
|
|
.gpu_memory_bytes = information.gpu_memory_bytes,
|
|
.cpu_threads = information.cpu_threads,
|
|
.gpu_slots = information.gpu_slots,
|
|
.io_slots = information.io_slots,
|
|
};
|
|
task->has_contract = true;
|
|
(void)clock_gettime(CLOCK_REALTIME, &task->started_at);
|
|
task->finished_at = (struct timespec) {0};
|
|
if (task->cancel_requested) {
|
|
Lardon3DResourceReservation *reservation_copy = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (reservation_copy) {
|
|
(void)lardon3d_resource_governor_release(
|
|
governor,
|
|
reservation_copy
|
|
);
|
|
}
|
|
notify_finished(task);
|
|
return true;
|
|
}
|
|
while (task->pause_requested && !task->cancel_requested) {
|
|
task->state = TASK_PAUSED;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
(void)pthread_cond_wait(&task->condition, &task->mutex);
|
|
}
|
|
if (task->cancel_requested) {
|
|
Lardon3DResourceReservation *reservation_copy = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (reservation_copy) {
|
|
(void)lardon3d_resource_governor_release(
|
|
governor,
|
|
reservation_copy
|
|
);
|
|
}
|
|
notify_finished(task);
|
|
return true;
|
|
}
|
|
task->state = TASK_RUNNING;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en cours.");
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
|
|
/* Callback is executed without task mutex held; caller-visible execution
|
|
* state transitions remain owned by task internals only.
|
|
* Finished callback is also issued after lock release.
|
|
*/
|
|
bool succeeded = task->callback(task, task->userdata);
|
|
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (task->cancel_requested) {
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
} else if (task->state == TASK_FAILED || !succeeded) {
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
task->message[0] ? NULL : "Échec de la tâche."
|
|
);
|
|
} else {
|
|
finish_locked(task, TASK_COMPLETED, "Tâche terminée.");
|
|
}
|
|
Lardon3DResourceReservation *reservation_copy = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (reservation_copy) {
|
|
(void)lardon3d_resource_governor_release(
|
|
governor,
|
|
reservation_copy
|
|
);
|
|
}
|
|
notify_finished(task);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_set_finished_callback(
|
|
Lardon3DTask *task,
|
|
Lardon3DTaskFinishedCallback callback,
|
|
void *userdata
|
|
)
|
|
{
|
|
if (!task || !callback) return false;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !task->executing && task->state == TASK_PENDING
|
|
&& !task->finished_callback;
|
|
if (accepted) {
|
|
task->finished_callback = callback;
|
|
task->finished_userdata = userdata;
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
void
|
|
lardon3d_task_request_cancel(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return;
|
|
}
|
|
bool finished_here = false;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (!is_terminal(task->state)) {
|
|
task->cancel_requested = true;
|
|
copy_text(task->message, sizeof(task->message), "Annulation demandée.");
|
|
if (!task->executing) {
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
finished_here = true;
|
|
}
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (finished_here) notify_finished(task);
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_pause(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !is_terminal(task->state) && !task->cancel_requested;
|
|
if (accepted) {
|
|
task->pause_requested = true;
|
|
if (!task->executing) {
|
|
task->state = TASK_PAUSED;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_resume(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !is_terminal(task->state) && task->pause_requested;
|
|
if (accepted) {
|
|
task->pause_requested = false;
|
|
if (!task->executing) {
|
|
task->state = TASK_PENDING;
|
|
copy_text(task->message, sizeof(task->message), "En attente.");
|
|
}
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_join(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
while (task->executing
|
|
|| (is_terminal(task->state) && task->finished_callback
|
|
&& (!task->finished_notified || task->finished_callback_running))) {
|
|
(void)pthread_cond_wait(&task->condition, &task->mutex);
|
|
}
|
|
bool terminal = is_terminal(task->state);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return terminal;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_checkpoint(Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
while (task->pause_requested && !task->cancel_requested) {
|
|
task->state = TASK_PAUSED;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
(void)pthread_cond_wait(&task->condition, &task->mutex);
|
|
}
|
|
if (!task->cancel_requested && task->executing) {
|
|
task->state = TASK_RUNNING;
|
|
}
|
|
bool continuing = !task->cancel_requested;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return continuing;
|
|
}
|
|
|
|
enum {
|
|
/* Attente bornée entre deux tentatives d'admission : 50 ms. */
|
|
LARDON3D_SEQUENCE_ADMISSION_WAIT_NS = 50000000ULL,
|
|
};
|
|
|
|
bool
|
|
lardon3d_task_sequence_break(
|
|
Lardon3DTask *task,
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceReservation **out_reservation,
|
|
Lardon3DTaskExecutionContract *out_contract
|
|
)
|
|
{
|
|
if (!task || !governor || !out_reservation || !out_contract) {
|
|
return false;
|
|
}
|
|
*out_reservation = NULL;
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (!task->executing || is_terminal(task->state)) {
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
while (task->pause_requested && !task->cancel_requested) {
|
|
task->state = TASK_PAUSED;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
(void)pthread_cond_wait(&task->condition, &task->mutex);
|
|
}
|
|
if (task->cancel_requested) {
|
|
Lardon3DResourceReservation *res = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (res) {
|
|
(void)lardon3d_resource_governor_release(governor, res);
|
|
}
|
|
return false;
|
|
}
|
|
task->state = TASK_RUNNING;
|
|
Lardon3DResourceReservation *previous = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
|
|
if (previous) {
|
|
(void)lardon3d_resource_governor_release(governor, previous);
|
|
}
|
|
for (;;) {
|
|
/* Vérifier pause et annulation avant chaque tentative d'admission. */
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
while (task->pause_requested && !task->cancel_requested) {
|
|
task->state = TASK_PAUSED;
|
|
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
|
|
(void)pthread_cond_broadcast(&task->condition);
|
|
(void)pthread_cond_wait(&task->condition, &task->mutex);
|
|
}
|
|
if (task->cancel_requested) {
|
|
Lardon3DResourceReservation *res = task->current_reservation;
|
|
task->current_reservation = NULL;
|
|
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (res) {
|
|
(void)lardon3d_resource_governor_release(governor, res);
|
|
}
|
|
return false;
|
|
}
|
|
task->state = TASK_RUNNING;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
|
|
uint64_t generation = lardon3d_resource_governor_generation(governor);
|
|
Lardon3DResourceDecision decision;
|
|
Lardon3DResourceReservation *next = NULL;
|
|
bool admitted = lardon3d_task_internal_reserve_available(
|
|
task,
|
|
governor,
|
|
&decision,
|
|
&next
|
|
);
|
|
if (!admitted) {
|
|
/* Erreur interne : échec d'allocation ou d'instantané. */
|
|
if (next) {
|
|
(void)lardon3d_resource_governor_release(governor, next);
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->current_reservation = NULL;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
"Impossible de réserver les ressources de la séquence."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
switch (decision.kind) {
|
|
case LARDON3D_RESOURCE_START:
|
|
case LARDON3D_RESOURCE_REDUCE_BATCH: {
|
|
if (!next) {
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->current_reservation = NULL;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
"Réservation de séquence invalide."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
Lardon3DResourceReservationInfo information;
|
|
if (!lardon3d_resource_reservation_get_active(
|
|
governor,
|
|
next,
|
|
&information
|
|
)) {
|
|
(void)lardon3d_resource_governor_release(governor, next);
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->current_reservation = NULL;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
"Réservation de séquence invalide."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
#ifdef LARDON3D_TASK_TESTING
|
|
if (task->test_force_sequence_association_mismatch) {
|
|
task->pending_reservation = NULL;
|
|
task->test_force_sequence_association_mismatch = false;
|
|
}
|
|
#endif
|
|
task->current_reservation = next;
|
|
if (task->pending_reservation != next) {
|
|
/* Association validation remains strict. Before releasing the
|
|
* rejected reservation, remove every Task ownership marker so
|
|
* task_start's common epilogue cannot release it a second time
|
|
* or expose a capability without its reservation. */
|
|
task->current_reservation = NULL;
|
|
task->pending_reservation = NULL;
|
|
task->pending_selection = (Lardon3DResourceCapabilitySelection) {0};
|
|
task->selected_capability = (Lardon3DResourceCapabilitySelection) {0};
|
|
task->has_selected_capability = false;
|
|
task->contract = (Lardon3DTaskExecutionContract) {0};
|
|
task->has_contract = false;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
"Sélection de capacité incohérente."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
bool released = lardon3d_resource_governor_release(governor, next);
|
|
#ifdef LARDON3D_TASK_TESTING
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
if (released) ++task->test_association_failure_releases;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
#else
|
|
(void)released;
|
|
#endif
|
|
return false;
|
|
}
|
|
task->selected_capability = task->pending_selection;
|
|
task->has_selected_capability = true;
|
|
task->pending_reservation = NULL;
|
|
task->contract = (Lardon3DTaskExecutionContract) {
|
|
.batch_size = information.batch_size,
|
|
.memory_bytes = information.memory_bytes,
|
|
.gpu_memory_bytes = information.gpu_memory_bytes,
|
|
.cpu_threads = information.cpu_threads,
|
|
.gpu_slots = information.gpu_slots,
|
|
.io_slots = information.io_slots,
|
|
};
|
|
// sequence_break releases one active reservation and acquires the
|
|
// next one before returning; tasks should continue only from a
|
|
// cleanly admitted boundary.
|
|
task->has_contract = true;
|
|
++task->sequence_count;
|
|
*out_reservation = next;
|
|
*out_contract = task->contract;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return true;
|
|
}
|
|
case LARDON3D_RESOURCE_REJECT:
|
|
if (next) {
|
|
(void)lardon3d_resource_governor_release(governor, next);
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->current_reservation = NULL;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
decision.reason[0] ? decision.reason
|
|
: "Ressources impossibles à réserver."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
case LARDON3D_RESOURCE_WAIT:
|
|
/* Indisponibilité temporaire : ne pas échouer, attendre un
|
|
* changement de ressources puis retenter l'admission. */
|
|
if (next) {
|
|
(void)lardon3d_resource_governor_release(governor, next);
|
|
}
|
|
(void)lardon3d_resource_governor_wait_for_change(
|
|
governor,
|
|
generation,
|
|
LARDON3D_SEQUENCE_ADMISSION_WAIT_NS
|
|
);
|
|
break;
|
|
default:
|
|
/* Décision inconnue : erreur interne, ne jamais boucler. */
|
|
if (next) {
|
|
(void)lardon3d_resource_governor_release(governor, next);
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->current_reservation = NULL;
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
"Décision de ressource inconnue."
|
|
);
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int
|
|
lardon3d_task_sequence_count(const Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return 0;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
unsigned int count = task->sequence_count;
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return count;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_set_progress(
|
|
Lardon3DTask *task,
|
|
unsigned int progress,
|
|
const char *message
|
|
)
|
|
{
|
|
if (!task || progress > 100) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !is_terminal(task->state);
|
|
if (accepted) {
|
|
task->progress = progress;
|
|
task->durable_progress_known = false;
|
|
task->durable_completed = 0;
|
|
task->durable_total = 0;
|
|
if (message) {
|
|
copy_text(task->message, sizeof(task->message), message);
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
static unsigned int
|
|
durable_percentage(uint64_t completed, uint64_t total)
|
|
{
|
|
if (completed >= total) {
|
|
return 100;
|
|
}
|
|
/* Compare against ceil(percent * total / 100). Splitting total first
|
|
* avoids overflowing uint64_t even at the public input maximum. */
|
|
uint64_t quotient = total / 100U;
|
|
uint64_t remainder = total % 100U;
|
|
for (unsigned int percent = 99; percent > 0; --percent) {
|
|
uint64_t threshold = quotient * percent;
|
|
uint64_t residual_product = remainder * percent;
|
|
threshold += residual_product / 100U;
|
|
if (residual_product % 100U != 0) {
|
|
++threshold;
|
|
}
|
|
if (completed >= threshold) {
|
|
return percent;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_set_durable_progress(
|
|
Lardon3DTask *task,
|
|
uint64_t completed,
|
|
uint64_t total,
|
|
const char *message
|
|
)
|
|
{
|
|
if (!task || total == 0 || completed > total) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
/* Only a typed Task owner can name a durable business prefix. Generic
|
|
* Task completion never fills the remaining count: doing so would invent
|
|
* a commit outside the typed transaction that owns this observation. */
|
|
bool accepted = !is_terminal(task->state) && task->task_kind[0] != '\0';
|
|
if (accepted) {
|
|
task->progress = durable_percentage(completed, total);
|
|
task->durable_progress_known = true;
|
|
task->durable_completed = completed;
|
|
task->durable_total = total;
|
|
if (message) {
|
|
copy_text(task->message, sizeof(task->message), message);
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_fail(Lardon3DTask *task, const char *message)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = task->executing && !is_terminal(task->state);
|
|
if (accepted) {
|
|
task->state = TASK_FAILED;
|
|
copy_text(
|
|
task->message,
|
|
sizeof(task->message),
|
|
message ? message : "Échec de la tâche."
|
|
);
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_snapshot(
|
|
const Lardon3DTask *task,
|
|
Lardon3DTaskSnapshot *snapshot
|
|
)
|
|
{
|
|
if (!task || !snapshot) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
*snapshot = (Lardon3DTaskSnapshot) {
|
|
.id = task->id,
|
|
.progress = task->progress,
|
|
.state = task->state,
|
|
.started_at = task->started_at,
|
|
.finished_at = task->finished_at,
|
|
};
|
|
copy_text(snapshot->name, sizeof(snapshot->name), task->name);
|
|
copy_text(snapshot->message, sizeof(snapshot->message), task->message);
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_observation(
|
|
const Lardon3DTask *task,
|
|
Lardon3DTaskObservation *observation
|
|
)
|
|
{
|
|
if (observation) {
|
|
*observation = (Lardon3DTaskObservation) {0};
|
|
}
|
|
if (!task || !observation) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
*observation = (Lardon3DTaskObservation) {
|
|
.id = task->id,
|
|
.progress = task->progress,
|
|
.state = task->state,
|
|
.started_at = task->started_at,
|
|
.finished_at = task->finished_at,
|
|
.has_task_kind = task->task_kind[0] != '\0',
|
|
.task_kind_version = task->task_kind_version,
|
|
.durable_progress_known = task->durable_progress_known,
|
|
.durable_completed = task->durable_progress_known
|
|
? task->durable_completed : 0,
|
|
.durable_total = task->durable_progress_known
|
|
? task->durable_total : 0,
|
|
.sequence_count = task->sequence_count,
|
|
.has_execution_contract = task->has_contract,
|
|
.execution_contract = task->has_contract
|
|
? task->contract
|
|
: (Lardon3DTaskExecutionContract) {0},
|
|
};
|
|
copy_text(observation->name, sizeof(observation->name), task->name);
|
|
copy_text(observation->task_kind, sizeof(observation->task_kind),
|
|
task->task_kind);
|
|
copy_text(observation->message, sizeof(observation->message),
|
|
task->message);
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_durable_snapshot(
|
|
const Lardon3DTask *task,
|
|
Lardon3DTaskDurableSnapshot *snapshot
|
|
)
|
|
{
|
|
if (!task || !snapshot) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
*snapshot = (Lardon3DTaskDurableSnapshot) {
|
|
.id = task->id,
|
|
.estimate = task->estimate,
|
|
.progress = task->progress,
|
|
.saved_state = task->state,
|
|
.recovery_state = recovery_state(task->state),
|
|
.started_at = task->started_at,
|
|
.finished_at = task->finished_at,
|
|
.sequence_count = task->sequence_count,
|
|
};
|
|
copy_text(snapshot->name, sizeof(snapshot->name), task->name);
|
|
copy_text(snapshot->message, sizeof(snapshot->message), task->message);
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return true;
|
|
}
|
|
|
|
Lardon3DTask *
|
|
lardon3d_task_restore(
|
|
const Lardon3DTaskDurableSnapshot *snapshot,
|
|
Lardon3DTaskCallback callback,
|
|
void *userdata
|
|
)
|
|
{
|
|
return lardon3d_task_restore_typed(
|
|
snapshot, NULL, 0, callback, userdata, NULL
|
|
);
|
|
}
|
|
|
|
Lardon3DTask *
|
|
lardon3d_task_restore_typed(
|
|
const Lardon3DTaskDurableSnapshot *snapshot,
|
|
const char *task_kind,
|
|
uint32_t task_kind_version,
|
|
Lardon3DTaskCallback callback,
|
|
void *userdata,
|
|
Lardon3DTaskUserdataDestroy userdata_destroy
|
|
)
|
|
{
|
|
if (!snapshot || snapshot->id == 0 || !snapshot->name[0] || !callback
|
|
|| memchr(snapshot->name, '\0', sizeof(snapshot->name)) == NULL
|
|
|| memchr(snapshot->message, '\0', sizeof(snapshot->message)) == NULL
|
|
|| snapshot->progress > 100 || !valid_state(snapshot->saved_state)
|
|
|| !valid_state(snapshot->recovery_state)
|
|
|| snapshot->recovery_state != recovery_state(snapshot->saved_state)
|
|
|| (snapshot->recovery_state == TASK_COMPLETED
|
|
&& snapshot->progress != 100)
|
|
|| snapshot->estimate.minimum_batch_size == 0
|
|
|| snapshot->estimate.maximum_batch_size
|
|
< snapshot->estimate.minimum_batch_size
|
|
|| snapshot->estimate.desired_cpu_threads == 0
|
|
|| snapshot->estimate.task_class < LARDON3D_RESOURCE_TASK_GENERAL
|
|
|| snapshot->estimate.task_class > LARDON3D_RESOURCE_TASK_MIXED
|
|
|| ((snapshot->estimate.gpu_memory_fixed_bytes != 0
|
|
|| snapshot->estimate.gpu_memory_bytes_per_item != 0)
|
|
&& snapshot->estimate.desired_gpu_slots == 0)) {
|
|
return NULL;
|
|
}
|
|
Lardon3DTask *task = lardon3d_task_create_typed(
|
|
snapshot->name,
|
|
&snapshot->estimate,
|
|
task_kind,
|
|
task_kind_version,
|
|
callback,
|
|
userdata,
|
|
userdata_destroy
|
|
);
|
|
if (!task) {
|
|
return NULL;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
task->id = snapshot->id;
|
|
task->progress = snapshot->progress;
|
|
task->state = snapshot->recovery_state;
|
|
copy_text(task->message, sizeof(task->message), snapshot->message);
|
|
task->started_at = snapshot->started_at;
|
|
task->finished_at = snapshot->finished_at;
|
|
task->sequence_count = snapshot->sequence_count;
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return task;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_kind(
|
|
const Lardon3DTask *task,
|
|
char task_kind[LARDON3D_TASK_KIND_CAPACITY],
|
|
uint32_t *task_kind_version
|
|
)
|
|
{
|
|
if (!task || !task_kind || !task_kind_version) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
bool typed = task->task_kind[0] != '\0';
|
|
if (typed) {
|
|
copy_text(task_kind, LARDON3D_TASK_KIND_CAPACITY, task->task_kind);
|
|
*task_kind_version = task->task_kind_version;
|
|
} else {
|
|
task_kind[0] = '\0';
|
|
*task_kind_version = 0;
|
|
}
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return typed;
|
|
}
|
|
|
|
uint64_t
|
|
lardon3d_task_id(const Lardon3DTask *task)
|
|
{
|
|
if (!task) {
|
|
return 0;
|
|
}
|
|
Lardon3DTaskSnapshot snapshot;
|
|
return lardon3d_task_snapshot(task, &snapshot) ? snapshot.id : 0;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_assign_id(Lardon3DTask *task, uint64_t id)
|
|
{
|
|
if (!task || id == 0) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = task->id == 0 && task->state == TASK_PENDING;
|
|
if (accepted) {
|
|
task->id = id;
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_resource_estimate(
|
|
const Lardon3DTask *task,
|
|
Lardon3DResourceEstimate *estimate
|
|
)
|
|
{
|
|
if (!task || !estimate) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
*estimate = task->estimate;
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_execution_contract(
|
|
const Lardon3DTask *task,
|
|
Lardon3DTaskExecutionContract *contract
|
|
)
|
|
{
|
|
if (!task || !contract) {
|
|
return false;
|
|
}
|
|
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
|
(void)pthread_mutex_lock(&mutable_task->mutex);
|
|
bool available = task->has_contract;
|
|
if (available) {
|
|
*contract = task->contract;
|
|
}
|
|
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
|
return available;
|
|
}
|
|
|
|
bool
|
|
lardon3d_task_reject(Lardon3DTask *task, const char *message)
|
|
{
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&task->mutex);
|
|
bool accepted = !task->executing && !is_terminal(task->state);
|
|
if (accepted) {
|
|
finish_locked(
|
|
task,
|
|
TASK_FAILED,
|
|
message ? message : "Ressources impossibles à réserver."
|
|
);
|
|
}
|
|
(void)pthread_mutex_unlock(&task->mutex);
|
|
if (accepted) notify_finished(task);
|
|
return accepted;
|
|
}
|
|
|
|
const char *
|
|
lardon3d_task_state_name(Lardon3DTaskState state)
|
|
{
|
|
switch (state) {
|
|
case TASK_PENDING:
|
|
return "En attente";
|
|
case TASK_RUNNING:
|
|
return "En cours";
|
|
case TASK_PAUSED:
|
|
return "En pause";
|
|
case TASK_CANCELLED:
|
|
return "Annulée";
|
|
case TASK_FAILED:
|
|
return "Échec";
|
|
case TASK_COMPLETED:
|
|
return "Terminée";
|
|
default:
|
|
return "Inconnu";
|
|
}
|
|
}
|