/*-------------------------------------------------------------------------
 *
 * ipc_test.c
 *	   Simplistic testbed for shared memory and semaphore code.
 *
 * This file allows for quick "smoke testing" of a PG semaphore or shared
 * memory implementation, with less overhead than compiling up a whole
 * installation.  To use:
 *	1. Run configure, then edit src/include/pg_config.h to select the
 *	   USE_xxx_SEMAPHORES and USE_xxx_SHARED_MEMORY settings you want.
 *	   Also, adjust the pg_sema.c and pg_shmem.c symlinks in
 *	   src/backend/port/ if needed.
 *	2. In src/backend/port/, do "gmake ipc_test".
 *	3. Run ipc_test and see if it works.
 *	4. If it seems to work, try building the whole system and running
 *	   the parallel regression tests for a more complete test.
 *
 *
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/port/ipc_test.c,v 1.3 2002/06/20 20:29:33 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <errno.h>
#include <unistd.h>

#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
#include "storage/pg_shmem.h"
#include "utils/exc.h"


/********* stuff needed to satisfy references in shmem/sema code *********/


volatile bool InterruptPending = false;
volatile bool QueryCancelPending = false;
volatile bool ProcDiePending = false;
volatile bool ImmediateInterruptOK = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;

bool		IsUnderPostmaster = false;

int			MaxBackends = DEF_MAXBACKENDS;
int			NBuffers = DEF_NBUFFERS;

#ifndef assert_enabled
bool         assert_enabled = true;
#endif

Exception	FailedAssertion = {"Failed Assertion"};


#define MAX_ON_EXITS 20

static struct ONEXIT
{
	void		(*function) ();
	Datum		arg;
}	on_proc_exit_list[MAX_ON_EXITS], on_shmem_exit_list[MAX_ON_EXITS];

static int	on_proc_exit_index,
			on_shmem_exit_index;

void
proc_exit(int code)
{
	shmem_exit(code);
	while (--on_proc_exit_index >= 0)
		(*on_proc_exit_list[on_proc_exit_index].function) (code,
							  on_proc_exit_list[on_proc_exit_index].arg);
	exit(code);
}

void
shmem_exit(int code)
{
	while (--on_shmem_exit_index >= 0)
		(*on_shmem_exit_list[on_shmem_exit_index].function) (code,
							on_shmem_exit_list[on_shmem_exit_index].arg);
	on_shmem_exit_index = 0;
}

void
on_shmem_exit(void (*function) (), Datum arg)
{
	if (on_shmem_exit_index >= MAX_ON_EXITS)
		elog(FATAL, "Out of on_shmem_exit slots");

	on_shmem_exit_list[on_shmem_exit_index].function = function;
	on_shmem_exit_list[on_shmem_exit_index].arg = arg;

	++on_shmem_exit_index;
}

void
on_exit_reset(void)
{
	on_shmem_exit_index = 0;
	on_proc_exit_index = 0;
}

void
RecordSharedMemoryInLockFile(unsigned long id1, unsigned long id2)
{
}

void
ProcessInterrupts(void)
{
}

int
ExceptionalCondition(char *conditionName,
					 Exception *exceptionP,
					 char *detail,
					 char *fileName,
					 int lineNumber)
{
	fprintf(stderr, "TRAP: %s(\"%s:%s\", File: \"%s\", Line: %d)\n",
			exceptionP->message, conditionName,
			(detail == NULL ? "" : detail),
			fileName, lineNumber);
	abort();
	return 0;
}

void
elog(int lev, const char *fmt,...)
{
	if (lev >= ERROR)
	{
		fprintf(stderr, "elog(%s)\n", fmt);
		abort();
	}
}


/********* here's the actual test *********/


typedef struct MyStorage
{
	PGShmemHeader	header;
	int				flag;
	PGSemaphoreData	sem;
} MyStorage;


int
main(int argc, char **argv)
{
	MyStorage	   *storage;
	int				cpid;

	printf("Creating shared memory ... ");
	fflush(stdout);

	storage = (MyStorage *) PGSharedMemoryCreate(8192, false, 5433);

	storage->flag = 1234;

	printf("OK\n");

	printf("Creating semaphores ... ");
	fflush(stdout);

	PGReserveSemaphores(2, 5433);

	PGSemaphoreCreate(&storage->sem);

	printf("OK\n");

	/* sema initial value is 1, so lock should work */

	printf("Testing Lock ... ");
	fflush(stdout);

	PGSemaphoreLock(&storage->sem, false);

	printf("OK\n");

	/* now sema value is 0, so trylock should fail */

	printf("Testing TryLock ... ");
	fflush(stdout);

	if (PGSemaphoreTryLock(&storage->sem))
		printf("unexpected result!\n");
	else
		printf("OK\n");

	/* unlocking twice and then locking twice should work... */

	printf("Testing Multiple Lock ... ");
	fflush(stdout);

	PGSemaphoreUnlock(&storage->sem);
	PGSemaphoreUnlock(&storage->sem);

	PGSemaphoreLock(&storage->sem, false);
	PGSemaphoreLock(&storage->sem, false);

	printf("OK\n");

	/* check Reset too */

	printf("Testing Reset ... ");
	fflush(stdout);

	PGSemaphoreUnlock(&storage->sem);

	PGSemaphoreReset(&storage->sem);

	if (PGSemaphoreTryLock(&storage->sem))
		printf("unexpected result!\n");
	else
		printf("OK\n");

	/* Fork a child process and see if it can communicate */

	printf("Forking child process ... ");
	fflush(stdout);

	cpid = fork();
	if (cpid == 0)
	{
		/* In child */
		on_exit_reset();
		sleep(3);
		storage->flag++;
		PGSemaphoreUnlock(&storage->sem);
		proc_exit(0);
	}
	if (cpid < 0)
	{
		/* Fork failed */
		printf("failed: %s\n", strerror(errno));
		proc_exit(1);
	}

	printf("forked child pid %d OK\n", cpid);

	if (storage->flag != 1234)
		printf("Wrong value found in shared memory!\n");

	printf("Waiting for child (should wait 3 sec here) ... ");
	fflush(stdout);

	PGSemaphoreLock(&storage->sem, false);

	printf("OK\n");

	if (storage->flag != 1235)
		printf("Wrong value found in shared memory!\n");

	/* Test shutdown */

	printf("Running shmem_exit processing ... ");
	fflush(stdout);

	shmem_exit(0);

	printf("OK\n");

	printf("Tests complete.\n");

	proc_exit(0);

	return 0;					/* not reached */
}
