#include "akerror.h" #include "err_capture.h" #include "err_threads.h" #include /* * The status registry under contention. * * Two properties, and they fail differently: * * - A reservation is a decision, not a write. The overlap scan and the entry * that follows it have to be one operation, or two threads claiming the * same range both find the table clear and both believe they own it. That * is silent: neither gets an error, and the collision surfaces much later * as one component's status rendering under another's name. The contested * range below is claimed by every thread at once and exactly one may win. * * - The name table is an open-addressed hash table with linear probing. A * concurrent insert that another thread's probe walks through -- an entry * half claimed, a count incremented before the slot was marked used -- loses * names or writes outside the table. So every thread registers a block of * names and reads each one back while the others are still writing, and * interleaves lookups of a name nobody is touching. * * Ownership enforcement has to hold under contention too: after every thread * has reserved, each one tries to name a status inside its neighbour's range * and must be refused. The barrier before that is what makes the expected * refusal exactly AKERR_STATUS_NAME_FOREIGN rather than sometimes * AKERR_STATUS_NAME_UNRESERVED, which is a real distinction and not just test * tidiness: FOREIGN means the registry knew who owned it. */ #define NAMES_PER_THREAD 64 #define CONTESTED_FIRST 900000 #define CONTESTED_COUNT 64 static int contested_winners; static int thread_range_base(int id) { return 500000 + (id * 1000); } static void *registry_body(void *raw) { akerr_ThreadArg *arg = raw; akerr_ErrorContext *e; char owner[32]; int base = thread_range_base(arg->id); int victim = thread_range_base((arg->id % AKERR_TEST_THREADS) + 1); snprintf(owner, sizeof(owner), "registry-%d", arg->id); pthread_barrier_wait(arg->barrier); /* One range, every thread, distinct owners. Exactly one may come back * successful; the rest must be told who won. */ e = akerr_reserve_status_range(CONTESTED_FIRST, CONTESTED_COUNT, owner); if ( e == NULL ) { __atomic_fetch_add(&contested_winners, 1, __ATOMIC_RELAXED); } else { AKERR_TCHECK(arg, e->status == AKERR_STATUS_RANGE_OVERLAP); AKERR_TCHECK(arg, strstr(e->message, "registry-") != NULL); RELEASE_ERROR(e); } /* This thread's own range, which nobody contests. */ e = akerr_reserve_status_range(base, NAMES_PER_THREAD, owner); AKERR_TCHECK(arg, e == NULL); RELEASE_ERROR(e); for ( int i = 0; i < NAMES_PER_THREAD; i++ ) { char name[48]; snprintf(name, sizeof(name), "registry-%d name %d", arg->id, i); e = akerr_register_status_name(owner, base + i, name); AKERR_TCHECK(arg, e == NULL); RELEASE_ERROR(e); /* Read it back while the other threads are still inserting. */ AKERR_TCHECK(arg, strcmp(akerr_name_for_status(base + i, NULL), name) == 0); /* And an entry nobody is touching: a probe sequence that a concurrent * insert walked off loses names that were already there. */ AKERR_TCHECK(arg, strcmp(akerr_name_for_status(AKERR_VALUE, NULL), "Value Error") == 0); } /* Everyone has reserved by the time anyone tries to trespass. */ pthread_barrier_wait(arg->barrier); e = akerr_register_status_name(owner, victim, "Hijack"); AKERR_TCHECK(arg, e != NULL); if ( e != NULL ) { AKERR_TCHECK(arg, e->status == AKERR_STATUS_NAME_FOREIGN); } RELEASE_ERROR(e); 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(®istry_body); AKERR_CHECK(failures == 0); /* The contested range went to exactly one owner. */ AKERR_CHECK(contested_winners == 1); /* Every name every thread registered is present and correct: nothing was * dropped, overwritten, or attributed to the wrong thread. */ for ( int id = 1; id <= AKERR_TEST_THREADS; id++ ) { int base = thread_range_base(id); for ( int i = 0; i < NAMES_PER_THREAD; i++ ) { char expected[48]; snprintf(expected, sizeof(expected), "registry-%d name %d", id, i); AKERR_CHECK(strcmp(akerr_name_for_status(base + i, NULL), expected) == 0); } /* The trespass attempt did not leave a name behind. */ AKERR_CHECK(strcmp(akerr_name_for_status(base, NULL), "Hijack") != 0); } /* The library's own entries survived every one of those inserts. */ AKERR_CHECK(strcmp(akerr_name_for_status(AKERR_VALUE, NULL), "Value Error") == 0); AKERR_CHECK(strcmp(akerr_name_for_status(AKERR_STATUS_NAME_FOREIGN, NULL), "Foreign Status Name") == 0); /* Thousands of refusals and registrations later, the pool is empty. */ AKERR_CHECK(akerr_slots_in_use() == 0); fprintf(stderr, "err_threads_registry ok (%d threads x %d names)\n", AKERR_TEST_THREADS, NAMES_PER_THREAD); return 0; }