Files
libakerror/tests/err_threads_pool.c
Logikoma (Codex GPT-5) eec5726d91
All checks were successful
libakerror CI Build / cmake_build (push) Successful in 2m52s
libakerror CI Build / coverage (push) Successful in 2m48s
libakerror CI Build / mutation_test (push) Successful in 44m14s
Release ignored error contexts
2026-08-03 12:55:42 -04:00

129 lines
4.3 KiB
C

#include "akerror.h"
#include "err_capture.h"
#include "err_threads.h"
#include <string.h>
/*
* The error pool under contention.
*
* AKERR_ARRAY_ERROR is a fixed 128-slot array shared by every thread in the
* process, and a slot is checked out by finding refcount == 0 and taking a
* reference. Those two steps have to be one operation: a scan that returned an
* unclaimed slot would hand the same one to every thread that scanned before
* the first of them incremented the count, and each would then format its own
* error into the same buffers. That failure is invisible to a single-threaded
* test and produces a garbled message rather than a crash, so this test asserts
* exclusivity directly.
*
* akerr_slot_owner[] (see err_threads.h) is the test's own record of who holds
* which slot, kept with atomics. Every check-out claims its slot and every
* release drops the claim; a slot handed to two threads at once is caught by
* the claim failing, whether or not the resulting message is garbled.
*
* Each thread also asserts that the error it raised is the error it handles,
* message and all. That is the same property from the other end: a context
* cannot be exclusively ours if another thread's text shows up in it.
*/
#define ITERATIONS 2000
/* Raise an error and take ownership of whatever slot it came from. */
static akerr_ErrorContext AKERR_NOIGNORE *boom(akerr_ThreadArg *arg)
{
PREPARE_ERROR(e);
FAIL(e, AKERR_VALUE, "raised by thread %d", arg->id);
AKERR_TCHECK(arg, akerr_slot_claim(e->arrayid, arg->id) == 0);
return e;
}
static akerr_ErrorContext AKERR_NOIGNORE *ignorable(akerr_ThreadArg *arg)
{
PREPARE_ERROR(e);
FAIL_RETURN(e, AKERR_IO, "ignored by thread %d", arg->id);
}
/* One raise -> catch -> handle cycle, exclusively owned from end to end. */
static void one_cycle(akerr_ThreadArg *arg)
{
char expected[64];
PREPARE_ERROR(e);
snprintf(expected, sizeof(expected), "raised by thread %d", arg->id);
ATTEMPT {
CATCH(e, boom(arg));
} CLEANUP {
} PROCESS(e) {
/* case 0: the error we just raised came back clean, which can only
* mean another thread wrote over this context. */
int error_was_lost = 1;
AKERR_TCHECK(arg, error_was_lost == 0);
} HANDLE(e, AKERR_VALUE) {
AKERR_TCHECK(arg, akerr_slot_holder(e->arrayid) == arg->id);
AKERR_TCHECK(arg, strcmp(e->message, expected) == 0);
AKERR_TCHECK(arg, strstr(e->stacktracebuf, expected) != NULL);
/* Give the slot up before FINISH releases the context: the other order
* hands it back to the pool while this thread still claims it. */
akerr_slot_drop(e->arrayid);
} FINISH_NORETURN(e);
}
/* The same property against the raw pool API, with no macros in between. */
static void one_checkout(akerr_ThreadArg *arg)
{
akerr_ErrorContext *e = akerr_next_error();
AKERR_TCHECK(arg, e != NULL);
if ( e == NULL ) {
return;
}
/* The context arrives already holding its reference. */
AKERR_TCHECK(arg, e->refcount == 1);
AKERR_TCHECK(arg, akerr_slot_claim(e->arrayid, arg->id) == 0);
AKERR_TCHECK(arg, akerr_slot_holder(e->arrayid) == arg->id);
akerr_slot_drop(e->arrayid);
RELEASE_ERROR(e);
AKERR_TCHECK(arg, e == NULL);
}
static void *pool_body(void *raw)
{
akerr_ThreadArg *arg = raw;
pthread_barrier_wait(arg->barrier);
for ( int i = 0; i < ITERATIONS; i++ ) {
one_cycle(arg);
one_checkout(arg);
}
/* IGNORE's scratch pointer is thread-local while logging and cleared after
* release. Concurrent ignored errors must all return their pool slots. */
IGNORE(ignorable(arg));
AKERR_TCHECK(arg, __akerr_last_ignored == NULL);
return NULL;
}
int main(void)
{
akerr_log_method = &akerr_thread_logger;
akerr_init();
AKERR_CHECK(akerr_slots_in_use() == 0);
int failures = akerr_run_threads(&pool_body);
AKERR_CHECK(failures == 0);
/* Every context went back to the pool, and every claim was dropped. */
AKERR_CHECK(akerr_slots_in_use() == 0);
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
AKERR_CHECK(akerr_slot_holder(i) == 0);
}
/* Each thread's IGNORE reported through the log method. */
AKERR_CHECK(akerr_thread_logs() >= AKERR_TEST_THREADS);
fprintf(stderr, "err_threads_pool ok (%d threads x %d cycles)\n",
AKERR_TEST_THREADS, ITERATIONS);
return 0;
}