125 lines
4.6 KiB
C
125 lines
4.6 KiB
C
|
|
#include "akerror.h"
|
||
|
|
#include "err_capture.h"
|
||
|
|
#include "err_threads.h"
|
||
|
|
#include <errno.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
/*
|
||
|
|
* 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;
|
||
|
|
}
|