#include "akerror.h" #include "err_capture.h" #include "err_threads.h" #include /* * 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; char expected[64]; snprintf(expected, sizeof(expected), "ignored by thread %d", arg->id); pthread_barrier_wait(arg->barrier); for ( int i = 0; i < ITERATIONS; i++ ) { one_cycle(arg); one_checkout(arg); } /* An ignored error is a fact about the thread that ignored it: each thread * must see its own, not the last one any thread swallowed. */ IGNORE(ignorable(arg)); AKERR_TCHECK(arg, __akerr_last_ignored != NULL); if ( __akerr_last_ignored != NULL ) { AKERR_TCHECK(arg, __akerr_last_ignored->status == AKERR_IO); AKERR_TCHECK(arg, strcmp(__akerr_last_ignored->message, expected) == 0); } /* IGNORE keeps the reference by design; hand it back so the pool is empty * at the end of the test. */ RELEASE_ERROR(__akerr_last_ignored); 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; }