
/*
 * define-race.c
 *
 * Program to exhibit a race in the current guile-1.8.6 define code.
 * See https://savannah.gnu.org/bugs/index.php?24867 for general 
 * status and description.
 *
 * Summary: variable definition and lookup is not thread-safe in guile;
 * attempting to look up a variable while another thread is defining 
 * a variable can sometimes lead to the first thread loosing, and not
 * seeing an existing, defined variable. Alternately, difining two
 * different variables at the same time can result in one of them
 * failing to be defined; on rarer occasions, a seg-fault results.
 *
 * Compile as:
 * cc define-race.c -lpthread -lguile
 *
 * May need to run several times to see the bug(s).
 *
 * Linas Vepstas <linasvepstas@gmail.com> December 2008
 */

#include <libguile.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
	int id;
	int count;
	int do_exit;
} state;

void * guile_mode_definer(void * ud)
{
	SCM result;
	int val;

	char buff[1000];
	state * s = (state *) ud;

	if (0 < s->count)
	{
		/* See if the previous definition holds the expected value. */
		sprintf (buff, "x%d-%d\n", s->id, s->count);
		result = scm_c_eval_string (buff);
		val = scm_to_int(result);

		if (val != s->count)
		{
			printf ("Define mismatch on thread %d\n", s->id);
		}
	}

	s->count ++;

	/* Define a new variable with a new value. */
	sprintf (buff, "(define x%d-%d %d)\n", s->id, s->count, s->count);
	// printf ("%s", buff);
	scm_c_eval_string (buff);
}

void * definer (void *ud)
{
	int i;
	state * s = (state *) ud;
	// while(!s->do_exit)
	for (i=0; i<4000; i++)
	{
		scm_with_guile(guile_mode_definer, ud);
		pthread_yield();  /* try to get the threads to inter-leave a lot */
	}
}

void init_ctr(state *s, int val)
{
	s->id = val;
	s->count = 0;
	s->do_exit = 0;
}


int main(int argc, char * argv)
{
	pthread_t th1, th2, th3, th4;
	state counter1, counter2, counter3, counter4;

	init_ctr (&counter1, 1);
	init_ctr (&counter2, 2);
	init_ctr (&counter3, 3);
	init_ctr (&counter4, 4);

	pthread_create(&th1, NULL, definer, (void *) &counter1);
	pthread_create(&th2, NULL, definer, (void *) &counter2);
	pthread_create(&th3, NULL, definer, (void *) &counter3);
	pthread_create(&th4, NULL, definer, (void *) &counter4);

	// sleep(1000);
	counter1.do_exit = 1;
	counter2.do_exit = 1;
	counter3.do_exit = 1;
	counter4.do_exit = 1;
	printf ("guile-define test case: good-bye!\n");

	pthread_join(th1, NULL);
	pthread_join(th2, NULL);
	pthread_join(th3, NULL);
	pthread_join(th4, NULL);
	exit(0);
}
