/****************************************************************
 *								*
 *	Copyright 2001, 2012 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#ifndef HAVE_CRIT_H_INCLUDED
#define HAVE_CRIT_H_INCLUDED

#include <signal.h>				/* needed for VSIG_ATOMIC_T */
#ifdef UNIX
#include <deferred_signal_handler.h>
#endif

/* states of CRIT passed as argument to have_crit() */
#define CRIT_IN_COMMIT		0x00000001
#define CRIT_NOT_TRANS_REG	0x00000002
#define CRIT_RELEASE		0x00000004
#define CRIT_ALL_REGIONS	0x00000008

#define	CRIT_IN_WTSTART		0x00000010	/* check if csa->in_wtstart is true */

/* Note absence of any flags is default value which finds if any region
 * or the replication pool have crit or are getting crit. It returns
 * when one is found without checking further.
*/
#define CRIT_HAVE_ANY_REG	0x00000000

#ifdef DEBUG
#include "wbox_test_init.h"
#endif
#ifdef UNIX
#include "gt_timer.h"
#endif

typedef enum
{
	INTRPT_OK_TO_INTERRUPT = 0,
	INTRPT_IN_GTCMTR_TERMINATE,
	INTRPT_IN_TP_UNWIND,
	INTRPT_IN_TP_CLEAN_UP,
	INTRPT_IN_CRYPT_SECTION,
	INTRPT_IN_DB_CSH_GETN,
	INTRPT_IN_GVCST_INIT,
	INTRPT_IN_GDS_RUNDOWN,
	INTRPT_IN_SS_INITIATE,
	INTRPT_IN_ZLIB_CMP_UNCMP,
	INTRPT_IN_TRIGGER_NOMANS_LAND,	/* State where have trigger base frame but no trigger (exec) frame */
	INTRPT_IN_MUR_OPEN_FILES,
	INTRPT_IN_TRUNC,
	INTRPT_IN_SET_NUM_ADD_PROCS,
	INTRPT_IN_SYSCONF,
	INTRPT_NO_TIMER_EVENTS,		/* State where primary reason for deferral is to avoid timer pops */
	INTRPT_IN_FFLUSH,		/* Deferring interrupts during fflush */
	INTRPT_IN_SHMDT,		/* Deferring interrupts during SHMDT */
	INTRPT_IN_WAIT_FOR_DISK_SPACE,	/* Deferring interrupts during wait_for_disk_space.c */
	INTRPT_NUM_STATES		/* Should be the *last* one in the enum */
} intrpt_state_t;

GBLREF	intrpt_state_t	intrpt_ok_state;
GBLREF	boolean_t	deferred_timers_check_needed;

/* Macro to check if we are in a state that is ok to interrupt (or to do deferred signal handling).
 * We do not want to interrupt if the global variable intrpt_ok_state indicates it is not ok to interrupt,
 * if we are in the midst of a malloc, if we are holding crit, if we are in the midst of commit, or in
 * wcs_wtstart. In the last case, we could be causing another process HOLDING CRIT on the region to wait
 * in bg_update_phase1 if we hold the write interlock. Hence it is important for us to finish that as soon
 * as possible and not interrupt it.
 */
#define	OK_TO_INTERRUPT	((INTRPT_OK_TO_INTERRUPT == intrpt_ok_state) && (0 == gtmMallocDepth)			\
				&& (0 == have_crit(CRIT_HAVE_ANY_REG | CRIT_IN_COMMIT | CRIT_IN_WTSTART)))

/* Macro to be used whenever we want to handle any signals that we deferred handling and exit in the process.
 * In VMS, we dont do any signal handling, only exit handling.
 */
#define	DEFERRED_EXIT_HANDLING_CHECK									\
{													\
	VMS_ONLY(GBLREF	int4	exi_condition;)								\
	GBLREF	int		process_exiting;							\
	GBLREF	VSIG_ATOMIC_T	forced_exit;								\
	GBLREF	volatile int4	gtmMallocDepth;								\
													\
	if (forced_exit)										\
	{												\
		if (!process_exiting && OK_TO_INTERRUPT)						\
		{											\
			UNIX_ONLY(deferred_signal_handler();)						\
			VMS_ONLY(sys$exit(exi_condition);)						\
		}											\
	} 												\
	UNIX_ONLY(											\
	else if (deferred_timers_check_needed)								\
	{												\
		if (!process_exiting && OK_TO_INTERRUPT)						\
			check_for_deferred_timers();							\
        }												\
	)												\
}

/* Macro to cause deferrable interrupts to be deferred recording the cause.
 * If interrupt is already deferred, state is not changed.
 *
 * The normal usage of the below macros is
 *	DEFER_INTERRUPTS
 *	non-interruptible code
 *	ENABLE_INTERRUPTS
 * We want the non-interruptible code to be executed AFTER the SAVE_INTRPT_OK_STATE macro.
 * To enforce this ordering, one would think a read memory barrier is needed in between.
 * But it is not needed. This is because we expect the non-interruptible code to have
 *	a) pointer dereferences OR
 *	b) function calls
 * Either of these will prevent the compiler from reordering the non-interruptible code.
 * Any non-interruptible code that does not have either of the above usages (for e.g. uses C global
 * variables) might be affected by compiler reordering. As of now, there is no known case of such
 * usage and no such usage is anticipated in the future.
 *
 * We dont need to worry about machine reordering as well since there is no shared memory variable
 * involved here (intrpt_ok_state is a process private variable) and even if any reordering occurs
 * they will all be in-flight instructions when the interrupt occurs so the hardware will guarantee
 * all such instructions are completely done or completely discarded before servicing the interrupt
 * which means the interrupt service routine will never see a reordered state of the above code.
 */
#define DEFER_INTERRUPTS(NEWSTATE)									\
{													\
	if (INTRPT_OK_TO_INTERRUPT == intrpt_ok_state)							\
		/* Only reset state if we are in "OK" state */						\
		intrpt_ok_state = NEWSTATE;								\
	else												\
		assert((NEWSTATE) != intrpt_ok_state);	/* Make sure not nesting same code */		\
}

/* Re-enable deferrable interrupts if the expected state is found. If expected state is not found, then
 * we must have nested interrupt types. Avoid state changes in that case. When the nested state pops,
 * interrupts will be restored.
 */
#define ENABLE_INTERRUPTS(OLDSTATE)									\
{													\
	assert(((OLDSTATE) == intrpt_ok_state) || (INTRPT_OK_TO_INTERRUPT != intrpt_ok_state));		\
	if ((OLDSTATE) == intrpt_ok_state)								\
	{	/* Only reset state if in expected state - othwise state must be non-zero which is	\
		 * asserted above.									\
		 */											\
		intrpt_ok_state = INTRPT_OK_TO_INTERRUPT;						\
		DEFERRED_EXIT_HANDLING_CHECK;	/* check if signals were deferred while held lock */	\
	}												\
}

uint4 have_crit(uint4 crit_state);

#endif /* HAVE_CRIT_H_INCLUDED */
