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