feat(governor): add adaptive batch sizing
This commit is contained in:
parent
d1292bf58e
commit
4a0b1df412
3 changed files with 365 additions and 3 deletions
|
|
@ -187,5 +187,15 @@ bool lardon3d_resource_governor_wait_for_change(
|
||||||
const char *lardon3d_resource_decision_name(
|
const char *lardon3d_resource_decision_name(
|
||||||
Lardon3DResourceDecisionKind kind
|
Lardon3DResourceDecisionKind kind
|
||||||
);
|
);
|
||||||
|
/* Enregistre les métriques d'un lot terminé pour l'adaptation dynamique
|
||||||
|
* de la taille des lots futurs. Le buffer est borné (8 entrées par classe
|
||||||
|
* de tâche). Thread-safe. */
|
||||||
|
bool lardon3d_resource_governor_record_batch(
|
||||||
|
Lardon3DResourceGovernor *governor,
|
||||||
|
Lardon3DResourceTaskClass task_class,
|
||||||
|
size_t batch_size,
|
||||||
|
uint64_t duration_ns,
|
||||||
|
size_t peak_memory_bytes
|
||||||
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ struct Lardon3DResourceReservation {
|
||||||
struct Lardon3DResourceReservation *next;
|
struct Lardon3DResourceReservation *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LARDON3D_BATCH_METRICS_CAPACITY = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t batch_size;
|
||||||
|
uint64_t duration_ns;
|
||||||
|
size_t peak_memory_bytes;
|
||||||
|
} Lardon3DBatchMetrics;
|
||||||
|
|
||||||
struct Lardon3DResourceGovernor {
|
struct Lardon3DResourceGovernor {
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
pthread_cond_t cond;
|
pthread_cond_t cond;
|
||||||
|
|
@ -24,6 +34,9 @@ struct Lardon3DResourceGovernor {
|
||||||
unsigned int cpu_reserved;
|
unsigned int cpu_reserved;
|
||||||
unsigned int gpu_slots_reserved;
|
unsigned int gpu_slots_reserved;
|
||||||
unsigned int io_slots_reserved;
|
unsigned int io_slots_reserved;
|
||||||
|
Lardon3DBatchMetrics batch_metrics[LARDON3D_RESOURCE_TASK_MIXED + 1][LARDON3D_BATCH_METRICS_CAPACITY];
|
||||||
|
size_t batch_metrics_count[LARDON3D_RESOURCE_TASK_MIXED + 1];
|
||||||
|
size_t batch_metrics_head[LARDON3D_RESOURCE_TASK_MIXED + 1];
|
||||||
size_t active_count;
|
size_t active_count;
|
||||||
Lardon3DResourceReservation *active;
|
Lardon3DResourceReservation *active;
|
||||||
Lardon3DResourceReservation *released;
|
Lardon3DResourceReservation *released;
|
||||||
|
|
@ -145,6 +158,94 @@ batch_capacity(uint64_t available, uint64_t fixed, uint64_t per_item)
|
||||||
return capacity > SIZE_MAX ? SIZE_MAX : (size_t)capacity;
|
return capacity > SIZE_MAX ? SIZE_MAX : (size_t)capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
record_batch_locked(
|
||||||
|
Lardon3DResourceGovernor *governor,
|
||||||
|
Lardon3DResourceTaskClass task_class,
|
||||||
|
size_t batch_size,
|
||||||
|
uint64_t duration_ns,
|
||||||
|
size_t peak_memory_bytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (batch_size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t class_index = (size_t)task_class;
|
||||||
|
if (class_index > (size_t)LARDON3D_RESOURCE_TASK_MIXED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DBatchMetrics *metrics = governor->batch_metrics[class_index];
|
||||||
|
size_t count = governor->batch_metrics_count[class_index];
|
||||||
|
size_t head = governor->batch_metrics_head[class_index];
|
||||||
|
metrics[head] = (Lardon3DBatchMetrics) {
|
||||||
|
.batch_size = batch_size,
|
||||||
|
.duration_ns = duration_ns,
|
||||||
|
.peak_memory_bytes = peak_memory_bytes,
|
||||||
|
};
|
||||||
|
head = (head + 1) % LARDON3D_BATCH_METRICS_CAPACITY;
|
||||||
|
governor->batch_metrics_head[class_index] = head;
|
||||||
|
if (count < LARDON3D_BATCH_METRICS_CAPACITY) {
|
||||||
|
governor->batch_metrics_count[class_index] = count + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
adaptive_batch_limit(
|
||||||
|
const Lardon3DResourceGovernor *governor,
|
||||||
|
Lardon3DResourceTaskClass task_class,
|
||||||
|
size_t static_batch,
|
||||||
|
uint64_t memory_bytes_per_item
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (memory_bytes_per_item == 0 || static_batch == 0) {
|
||||||
|
return static_batch;
|
||||||
|
}
|
||||||
|
size_t class_index = (size_t)task_class;
|
||||||
|
if (class_index > (size_t)LARDON3D_RESOURCE_TASK_MIXED) {
|
||||||
|
return static_batch;
|
||||||
|
}
|
||||||
|
size_t count = governor->batch_metrics_count[class_index];
|
||||||
|
if (count == 0) {
|
||||||
|
return static_batch;
|
||||||
|
}
|
||||||
|
size_t head = governor->batch_metrics_head[class_index];
|
||||||
|
uint64_t measured_per_item = 0;
|
||||||
|
size_t samples = 0;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
size_t idx = (head + LARDON3D_BATCH_METRICS_CAPACITY - count + i)
|
||||||
|
% LARDON3D_BATCH_METRICS_CAPACITY;
|
||||||
|
const Lardon3DBatchMetrics *m = &governor->batch_metrics[class_index][idx];
|
||||||
|
if (m->batch_size > 0 && m->peak_memory_bytes > 0) {
|
||||||
|
/* Coût par élément le plus défavorable observé : une moyenne
|
||||||
|
* sous-estimerait le pic et laisserait un lot dépasser son
|
||||||
|
* budget. La stabilité de l'hôte prime sur le débit. */
|
||||||
|
uint64_t per_item = m->peak_memory_bytes / m->batch_size
|
||||||
|
+ (m->peak_memory_bytes % m->batch_size != 0);
|
||||||
|
if (per_item > measured_per_item) {
|
||||||
|
measured_per_item = per_item;
|
||||||
|
}
|
||||||
|
++samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (samples == 0) {
|
||||||
|
return static_batch;
|
||||||
|
}
|
||||||
|
if (measured_per_item <= memory_bytes_per_item) {
|
||||||
|
return static_batch;
|
||||||
|
}
|
||||||
|
/* Éviter l'overflow de la multiplication : si static_batch est trop
|
||||||
|
* grand pour être multiplié sans débordement, on retourne 1 (le lot le
|
||||||
|
* plus conservateur possible) plutôt que de saturer à SIZE_MAX. */
|
||||||
|
if (static_batch > UINT64_MAX / memory_bytes_per_item) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
uint64_t corrected = (uint64_t)static_batch * memory_bytes_per_item
|
||||||
|
/ measured_per_item;
|
||||||
|
size_t result = corrected > SIZE_MAX ? SIZE_MAX : (size_t)corrected;
|
||||||
|
return result > 0 ? result : 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_decision(
|
set_decision(
|
||||||
Lardon3DResourceDecision *decision,
|
Lardon3DResourceDecision *decision,
|
||||||
|
|
@ -524,7 +625,20 @@ evaluate_locked(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
batch = minimum_size(batch, estimate->maximum_batch_size);
|
/* Le lot maximal visé est corrigé par les métriques mesurées : c'est la
|
||||||
|
* nouvelle cible du contrat, pas une réduction faute de ressources. La
|
||||||
|
* correction ne descend jamais sous minimum_batch_size pour éviter un
|
||||||
|
* WAIT persistant. */
|
||||||
|
size_t adapted_maximum = adaptive_batch_limit(
|
||||||
|
governor,
|
||||||
|
estimate->task_class,
|
||||||
|
estimate->maximum_batch_size,
|
||||||
|
memory_per_item
|
||||||
|
);
|
||||||
|
if (adapted_maximum < estimate->minimum_batch_size) {
|
||||||
|
adapted_maximum = estimate->minimum_batch_size;
|
||||||
|
}
|
||||||
|
batch = minimum_size(batch, adapted_maximum);
|
||||||
if (batch < estimate->minimum_batch_size) {
|
if (batch < estimate->minimum_batch_size) {
|
||||||
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Ressources déjà réservées ou temporairement insuffisantes.");
|
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Ressources déjà réservées ou temporairement insuffisantes.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -547,7 +661,7 @@ evaluate_locked(
|
||||||
unsigned int io = estimate->desired_io_slots < available.io_slots_available
|
unsigned int io = estimate->desired_io_slots < available.io_slots_available
|
||||||
? estimate->desired_io_slots
|
? estimate->desired_io_slots
|
||||||
: available.io_slots_available;
|
: available.io_slots_available;
|
||||||
bool reduced = batch < estimate->maximum_batch_size
|
bool reduced = batch < adapted_maximum
|
||||||
|| cpu < estimate->desired_cpu_threads
|
|| cpu < estimate->desired_cpu_threads
|
||||||
|| gpu < estimate->desired_gpu_slots
|
|| gpu < estimate->desired_gpu_slots
|
||||||
|| io < estimate->desired_io_slots;
|
|| io < estimate->desired_io_slots;
|
||||||
|
|
@ -820,6 +934,38 @@ lardon3d_resource_governor_reservation_count(
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lardon3d_resource_governor_record_batch(
|
||||||
|
Lardon3DResourceGovernor *governor,
|
||||||
|
Lardon3DResourceTaskClass task_class,
|
||||||
|
size_t batch_size,
|
||||||
|
uint64_t duration_ns,
|
||||||
|
size_t peak_memory_bytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!governor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (batch_size == 0) {
|
||||||
|
/* No-op réussi : aucune métrique, aucun réveil inutile. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_lock(&governor->mutex);
|
||||||
|
bool recorded = record_batch_locked(
|
||||||
|
governor,
|
||||||
|
task_class,
|
||||||
|
batch_size,
|
||||||
|
duration_ns,
|
||||||
|
peak_memory_bytes
|
||||||
|
);
|
||||||
|
if (recorded) {
|
||||||
|
++governor->generation;
|
||||||
|
(void)pthread_cond_broadcast(&governor->cond);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&governor->mutex);
|
||||||
|
return recorded;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
lardon3d_resource_governor_decide(
|
lardon3d_resource_governor_decide(
|
||||||
Lardon3DResourceGovernor *governor,
|
Lardon3DResourceGovernor *governor,
|
||||||
|
|
|
||||||
|
|
@ -391,10 +391,216 @@ run_generation_test(void)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
run_adaptive_batch_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);
|
||||||
|
|
||||||
|
Lardon3DResourceSnapshot snapshot = {
|
||||||
|
.memory_available_bytes = GIBIBYTES(10),
|
||||||
|
.cpu_load_1m = 2.0,
|
||||||
|
};
|
||||||
|
Lardon3DResourceDecision decision;
|
||||||
|
|
||||||
|
/* Test 1: Without metrics, batch is unchanged */
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 8);
|
||||||
|
|
||||||
|
/* Test 2: Record batch matching estimate → no reduction */
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
8,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(800)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 8);
|
||||||
|
|
||||||
|
/* Test 3: Record batch with higher memory usage → batch reduced */
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
8,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(1600)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 4);
|
||||||
|
|
||||||
|
/* Test 4: Buffer overflow (9 writes) → oldest overwritten */
|
||||||
|
for (size_t i = 0; i < 9; ++i) {
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
4,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(400)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 8);
|
||||||
|
|
||||||
|
/* Test 5: Different task class not affected */
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_IO,
|
||||||
|
8,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(1600)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 2,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 8);
|
||||||
|
|
||||||
|
/* Test 6: record_batch increments generation */
|
||||||
|
uint64_t gen_before = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
4,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(400)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == gen_before + 1);
|
||||||
|
|
||||||
|
/* Test 7: NULL governor returns false */
|
||||||
|
CHECK(!lardon3d_resource_governor_record_batch(NULL, LARDON3D_RESOURCE_TASK_GENERAL, 4, 0, 0));
|
||||||
|
|
||||||
|
/* Test 8: Zero batch_size is ignored */
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(governor, LARDON3D_RESOURCE_TASK_GENERAL, 0, 0, 0));
|
||||||
|
|
||||||
|
/* T1: Invalid task class → false, generation unchanged */
|
||||||
|
gen_before = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(!lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
(Lardon3DResourceTaskClass)999,
|
||||||
|
4,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(400)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == gen_before);
|
||||||
|
|
||||||
|
/* T2: batch_size == 0 → true, generation unchanged */
|
||||||
|
gen_before = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == gen_before);
|
||||||
|
|
||||||
|
/* T3: minimum_batch_size guaranteed */
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
8,
|
||||||
|
1000000000ULL,
|
||||||
|
GIBIBYTES(8)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = MEBIBYTES(100),
|
||||||
|
.minimum_batch_size = 4,
|
||||||
|
.preferred_batch_size = 8,
|
||||||
|
.requested_cpu_threads = 4,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size == 4);
|
||||||
|
|
||||||
|
/* T4: Overflow of static_batch * memory_bytes_per_item in uint64_t.
|
||||||
|
* With memory_bytes_per_item = 2 and preferred_batch_size = SIZE_MAX,
|
||||||
|
* the multiplication SIZE_MAX * 2 would overflow uint64_t. The overflow
|
||||||
|
* guard in adaptive_batch_limit returns 1 (most conservative). The result
|
||||||
|
* is never SIZE_MAX, the adaptive limit never increases the batch, and
|
||||||
|
* the final result is clamped to the minimum expected value. */
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_GENERAL,
|
||||||
|
1,
|
||||||
|
1000000000ULL,
|
||||||
|
100
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &(Lardon3DResourceRequest) {
|
||||||
|
.memory_bytes_per_item = 2,
|
||||||
|
.minimum_batch_size = 1,
|
||||||
|
.preferred_batch_size = SIZE_MAX,
|
||||||
|
.requested_cpu_threads = 1,
|
||||||
|
}, &decision));
|
||||||
|
CHECK(decision.kind == LARDON3D_RESOURCE_START);
|
||||||
|
CHECK(decision.batch_size != SIZE_MAX);
|
||||||
|
CHECK(decision.batch_size >= 1);
|
||||||
|
CHECK(decision.batch_size <= 1);
|
||||||
|
|
||||||
|
/* T5: generation increment only on real record */
|
||||||
|
gen_before = lardon3d_resource_governor_generation(governor);
|
||||||
|
CHECK(lardon3d_resource_governor_record_batch(
|
||||||
|
governor,
|
||||||
|
LARDON3D_RESOURCE_TASK_IO,
|
||||||
|
4,
|
||||||
|
1000000000ULL,
|
||||||
|
MEBIBYTES(400)
|
||||||
|
));
|
||||||
|
CHECK(lardon3d_resource_governor_generation(governor) == gen_before + 1);
|
||||||
|
|
||||||
|
lardon3d_resource_governor_destroy(governor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void)
|
main(void)
|
||||||
{
|
{
|
||||||
return (run_test() && run_generation_test())
|
return (run_test() && run_generation_test() && run_adaptive_batch_test())
|
||||||
? EXIT_SUCCESS
|
? EXIT_SUCCESS
|
||||||
: EXIT_FAILURE;
|
: EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue