#include "akerror.h" #include "err_capture.h" #include "err_threads.h" #include #include /* * akerr_init() runs exactly once, no matter how many threads reach the library * at the same instant. * * This is the hardest of the three to get right, because initialization is * re-entrant: akerr_init() reserves the library's own status band and names its * own codes, and every one of those calls goes through a public entry point * that calls akerr_init() again. The guard against that recursion has to be * per-thread, or a second thread arriving mid-initialization would see the flag * the first thread raised on its way in and walk tables that are still being * built. * * Nothing in main() touches the library before the threads start, so the race * is real: whichever thread wins does the initializing, and the rest must block * until it is finished rather than proceed on half-built tables. * * What proves it ran once rather than several times: each thread reserves its * own status range as its first act. A second pass through akerr_init() would * memset the range table, so a reservation made by a thread that raced ahead * would silently vanish -- exactly the failure the pre-1.0.0 library had. After * the join, every thread's reservation must still be there, still attributed to * that thread. */ static int thread_range_base(int id) { return 400000 + (id * 16); } static void *init_racer(void *raw) { akerr_ThreadArg *arg = raw; akerr_ErrorContext *e; char owner[32]; char name[32]; int base = thread_range_base(arg->id); snprintf(owner, sizeof(owner), "init-thread-%d", arg->id); snprintf(name, sizeof(name), "Thread %d Error", arg->id); pthread_barrier_wait(arg->barrier); /* First touch of the library from this thread, and for one of them the * first touch in the process. */ e = akerr_reserve_status_range(base, 16, owner); AKERR_TCHECK(arg, e == NULL); RELEASE_ERROR(e); e = akerr_register_status_name(owner, base, name); AKERR_TCHECK(arg, e == NULL); RELEASE_ERROR(e); /* The library's own band was reserved by whichever thread initialized, and * every thread must see it as taken -- including the one that did it. A * second initialization would have wiped the reservation and let this * through. */ e = akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT, owner); AKERR_TCHECK(arg, e != NULL); if ( e != NULL ) { AKERR_TCHECK(arg, e->status == AKERR_STATUS_RANGE_OVERLAP); AKERR_TCHECK(arg, strstr(e->message, AKERR_LIBRARY_OWNER) != NULL); } RELEASE_ERROR(e); /* The name tables are complete as seen from every thread: the library's own * codes, the generated errno names, and this thread's own registration. */ AKERR_TCHECK(arg, strcmp(akerr_name_for_status(AKERR_VALUE, NULL), "Value Error") == 0); AKERR_TCHECK(arg, strcmp(akerr_name_for_status(AKERR_NULLPOINTER, NULL), "Null Pointer Error") == 0); AKERR_TCHECK(arg, strcmp(akerr_name_for_status(EACCES, NULL), "Unknown Error") != 0); AKERR_TCHECK(arg, strcmp(akerr_name_for_status(base, NULL), name) == 0); /* And an error raised from this thread renders with a name, which is the * whole point of the tables being complete. */ PREPARE_ERROR(errctx); ATTEMPT { FAIL_BREAK(errctx, AKERR_TYPE, "raised during init race by thread %d", arg->id); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_TYPE) { AKERR_TCHECK(arg, strstr(errctx->stacktracebuf, "Type Error") != NULL); } FINISH_NORETURN(errctx); return NULL; } int main(void) { /* Installed before anything initializes: akerr_init() only supplies the * default logger when this is still NULL. */ akerr_log_method = &akerr_thread_logger; int failures = akerr_run_threads(&init_racer); AKERR_CHECK(failures == 0); /* Every thread's reservation survived the race, under its own owner. */ for ( int id = 1; id <= AKERR_TEST_THREADS; id++ ) { char owner[32]; char name[32]; int base = thread_range_base(id); snprintf(owner, sizeof(owner), "init-thread-%d", id); snprintf(name, sizeof(name), "Thread %d Error", id); AKERR_CHECK(strcmp(akerr_name_for_status(base, NULL), name) == 0); AKERR_CHECK_RAISES(akerr_reserve_status_range(base, 16, "verifier"), AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_MESSAGE_CONTAINS(owner); } /* Nothing leaked a pool slot on the way through. */ AKERR_CHECK(akerr_slots_in_use() == 0); fprintf(stderr, "err_threads_init ok (%d threads raced initialization)\n", AKERR_TEST_THREADS); return 0; }