
/* Test creation and removal of failure points while checking them on a
 * different thread.
 *
 * Please note this is a non-deterministic test. */

#include <assert.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fiu-control.h>
#include <fiu.h>

#define NPOINTS 10000

/* How many seconds to run the test */
#define TEST_TIME 5

/* Signal for the threads to stop. */
bool stop_threads = false;

/* The name of each point */
char point_name[NPOINTS][16];

char *make_point_name(char *name, int i)
{
	sprintf(name, "fp-%d", i);
	return name;
}

/* Calls all the points all the time. */
unsigned long long no_check_caller_count = 0;
void *no_check_caller(void *unused)
{
	int i = 0;

	while (!stop_threads) {
		fiu_fail(point_name[i]);

		i = (i + 1) % NPOINTS;

		if (i == 0)
			no_check_caller_count++;
	}

	no_check_caller_count = no_check_caller_count * NPOINTS + i;

	return NULL;
}

bool rand_bool(void)
{
	return (rand() % 2) == 0;
}

/* Used too know if a point is enabled or not. */
bool enabled[NPOINTS];
pthread_rwlock_t enabled_lock;

/* Calls all the *enabled* points all the time. */
unsigned long long checking_caller_count = 0;
void *checking_caller(void *unused)
{
	int i = 0;
	int failed;

	while (!stop_threads) {
		pthread_rwlock_rdlock(&enabled_lock);
		if (enabled[i]) {
			failed = fiu_fail(point_name[i]) != 0;
			pthread_rwlock_unlock(&enabled_lock);

			if (!failed) {
				printf("ERROR: fp %d did not fail\n", i);
				assert(false);
			}
		} else {
			pthread_rwlock_unlock(&enabled_lock);
		}

		i = (i + 1) % NPOINTS;

		if (i == 0)
			checking_caller_count++;
	}

	checking_caller_count = checking_caller_count * NPOINTS + i;

	return NULL;
}

/* Enable and disable points all the time. */
unsigned long long enabler_count = 0;
void *enabler(void *unused)
{
	int i = 0;

	while (!stop_threads) {
		if (rand_bool()) {
			pthread_rwlock_wrlock(&enabled_lock);
			if (enabled[i]) {
				assert(fiu_disable(point_name[i]) == 0);
				enabled[i] = false;
			} else {
				assert(fiu_enable(point_name[i], 1, NULL, 0) ==
				       0);
				enabled[i] = true;
			}
			pthread_rwlock_unlock(&enabled_lock);
		}

		i = (i + 1) % NPOINTS;

		if (i == 0)
			enabler_count++;
	}

	enabler_count = enabler_count * NPOINTS + i;

	return NULL;
}

void disable_all()
{
	int i = 0;

	pthread_rwlock_wrlock(&enabled_lock);
	for (i = 0; i < NPOINTS; i++) {
		/* Note this could fail as we don't check if they're active or
		 * not, but here we don't care. */
		fiu_disable(point_name[i]);
	}
	pthread_rwlock_unlock(&enabled_lock);
}

int main(void)
{
	int i;

	pthread_t t1, t2, t3;

	fiu_init(0);

	for (i = 0; i < NPOINTS; i++)
		make_point_name(point_name[i], i);

	pthread_rwlock_init(&enabled_lock, NULL);

	pthread_create(&t1, NULL, no_check_caller, NULL);
	pthread_create(&t2, NULL, checking_caller, NULL);
	pthread_create(&t3, NULL, enabler, NULL);

	sleep(TEST_TIME);

	stop_threads = 1;

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);

	disable_all();

	pthread_rwlock_destroy(&enabled_lock);

	printf("parallel nc: %-8llu  c: %-8llu  e: %-8llu  t: %llu\n",
	       no_check_caller_count, checking_caller_count, enabler_count,
	       no_check_caller_count + checking_caller_count + enabler_count);

	return 0;
}
