/****************************************************************
 *								*
 *	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.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "gdscc.h"
#include "gdskill.h"
#include "filestruct.h"
#include "jnl.h"
#include "buddy_list.h"		/* needed for tp.h */
#include "hashtab_int4.h"	/* needed for tp.h */
#include "tp.h"
#include "tp_frame.h"
#include "tp_timeout.h"
#include "lv_val.h"		/* needed for tp_unwind.h */
#include "tp_unwind.h"
#include "op.h"
#include "jobinterrupt_process.h"

GBLREF	uint4			dollar_tlevel;
GBLREF	uint4			dollar_trestart;
GBLREF	gv_key			*gv_currkey;
GBLREF	gv_namehead		*gv_target;
GBLREF	tp_region		*tp_reg_list;	/* Chained list of regions used in this transaction not cleared on tp_restart */
GBLREF	gd_region		*gv_cur_region;
GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	void			(*tp_timeout_clear_ptr)(void);
GBLREF	int			process_exiting;
#ifdef GTM_TRIGGER
GBLREF	int4			gtm_trigger_depth;
GBLREF	int4			tstart_trigger_depth;
#endif
#ifdef DEBUG
GBLREF	boolean_t		donot_INVOKE_MUMTSTART;
#endif
GBLREF	boolean_t		implicit_trollback;
GBLREF	tp_frame		*tp_pointer;

error_def(ERR_TLVLZERO);
error_def(ERR_TROLLBK2DEEP);
error_def(ERR_INVROLLBKLVL);

#define	RESTORE_GV_CUR_REGION						\
{									\
	gv_cur_region = save_cur_region;				\
	TP_CHANGE_REG(gv_cur_region);					\
}

void	op_trollback(int rb_levels)		/* rb_levels -> # of transaction levels by which we need to rollback : BYPASSOK */
{
	boolean_t	lcl_implicit_trollback = FALSE;
	uint4		newlevel;
	gd_region	*save_cur_region;	/* saved copy of gv_cur_region before tp_clean_up/tp_incr_clean_up modifies it */
	gd_region	*curreg;
	gv_key		*gv_orig_key_ptr;
	sgmnt_addrs	*csa;
	tp_region	*tr;

	if (implicit_trollback)
	{
		/* Unlike the call to "op_trollback" from generated code, this invocation of "op_trollback" is from C runtime code.
		 * Set the global variable to FALSE right away to avoid incorrect values from persisting in case of errors
		 * down below. Before the reset of the global variable, copy it into a local variable.
		 */
		lcl_implicit_trollback = implicit_trollback;
		implicit_trollback = FALSE;
	}
	if (!dollar_tlevel)
		rts_error(VARLSTCNT(1) ERR_TLVLZERO);
	if (0 > rb_levels)
	{
		if (dollar_tlevel < -rb_levels)
			rts_error(VARLSTCNT(4) ERR_TROLLBK2DEEP, 2, -rb_levels, dollar_tlevel);
	} else if (dollar_tlevel <= rb_levels)
		rts_error(VARLSTCNT(4) ERR_INVROLLBKLVL, 2, rb_levels, dollar_tlevel);
	newlevel = (0 > rb_levels) ? dollar_tlevel + rb_levels : rb_levels;
	/* The DBG_CHECK_GVTARGET_CSADDRS_IN_SYNC macro is used at various points in the database code to check that
	 * gv_target and cs_addrs are in sync. This is because op_gvname relies on this in order to avoid a gv_bind_name
	 * function call (if incoming key matches gv_currkey from previous call, it uses gv_target and cs_addrs right
	 * away instead of recomputing them). We want to check that here as well. The only exception is if we were
	 * interrupted in the middle of TP transaction by an external signal which resulted in us terminating right away.
	 * In this case, we are guaranteed not to make a call to op_gvname again (because we are exiting) so it is ok
	 * not to do this check.
	 */
	DEBUG_ONLY(
		if (!process_exiting)
		{
			DBG_CHECK_GVTARGET_CSADDRS_IN_SYNC;
		}
	)
	save_cur_region = gv_cur_region;
	GTMTRIG_ONLY(assert(tstart_trigger_depth <= gtm_trigger_depth);) /* see similar assert in op_tcommit.c for why */
	if (!newlevel)
	{
		(*tp_timeout_clear_ptr)();	/* Cancel or clear any pending TP timeout */
		/* Do a rollback type cleanup (invalidate gv_target clues of read as well as
		 * updated blocks). This is typically needed for a restart.
		 */
		tp_clean_up(TRUE);
		for (tr = tp_reg_list;  NULL != tr;  tr = tr->fPtr)
		{
			curreg = tr->reg;
			if (!curreg->open)
				continue;
			csa = &FILE_INFO(curreg)->s_addrs;
			INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_rolledback, 1);
			if (csa->now_crit && !csa->hold_onto_crit)
				rel_crit(curreg);			/* release any crit regions */
		}
		if (lcl_implicit_trollback && tp_pointer->implicit_tstart)
		{	/* This is an implicit TROLLBACK of an implicit TSTART started for a non-tp explicit update.
			 * gv_currkey needs to be restored to the value it was at the beginning of the implicit TSTART.
			 * This is necessary so as to maintain $reference accurately in case of an error during the
			 * ^#t processing initiated by an explicit non-tp update.
			 */
			assert(NULL != gv_currkey);
			assert(tp_pointer && tp_pointer->orig_key);
			gv_orig_key_ptr = tp_pointer->orig_key;
			/* At this point we expect tp_pointer->orig_key and gv_currkey to be in sync. However there are two
			 * exceptions to this.
			 * (a) If MUPIP TRIGGER detects that all of the triggers are erroneous and attempts an "op_trollback",
			 *     gv_currkey would be pointing to ^#t. However, tp_pointer->orig_key would remain at "" (initialized
			 *     during op_tstart).
			 * (b) If an M program defines $etrap to do a halt and an explicit Non-TP update causes a trigger to be
			 *     executed that further causes a runtime error which will now invoke the $etrap code. Since the
			 *     $etrap code does a halt, gtm_exit_handler will invoke "op_trollback" (since dollar_tlevel is > 0).
			 *     At this point, tp_pointer->orig_key and gv_currkey need not be in sync.
			 * To maintain $reference accurately, we need to restore gv_currkey from tp_pointer->orig_key.
			 */
			COPY_KEY(gv_currkey, gv_orig_key_ptr);
		} else if (gv_currkey != NULL)
		{
			gv_currkey->base[0] = '\0';
			gv_currkey->end = gv_currkey->prev = 0;
		}
		if (NULL != gv_target)
			gv_target->clue.end = 0;
		tp_unwind(newlevel, ROLLBACK_INVOCATION, NULL);
		/* Now that we are out of TP, reset the debug-only global variable that is relevant only if we are in TP */
		DEBUG_ONLY(donot_INVOKE_MUMTSTART = FALSE;)
		dollar_trestart = 0;
		RESTORE_GV_CUR_REGION;
		JOBINTR_TP_RETHROW; /* rethrow job interrupt($ZINT) if $ZTEXIT, when coerced to boolean, is true */
	} else
	{
		tp_incr_clean_up(newlevel);
		if (gv_currkey != NULL)
		{
			gv_currkey->base[0] = '\0';
			gv_currkey->end = gv_currkey->prev = 0;
		}
		RESTORE_GV_CUR_REGION;
		tp_unwind(newlevel, ROLLBACK_INVOCATION, NULL);
	}
	DEBUG_ONLY(
		if (!process_exiting)
		{
			DBG_CHECK_GVTARGET_CSADDRS_IN_SYNC;
		}
	)
}
