/****************************************************************
 *								*
 *	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 "gtm_inet.h"
#ifdef VMS
#include <descrip.h> /* Required for gtmsource.h */
#endif

#include "gdsroot.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "repl_msg.h"
#include "gtmsource.h"
#include "filestruct.h"
#include "jnl.h"
#include "dpgbldir.h"
#include "have_crit.h"
#include "send_msg.h"

GBLREF volatile int4		crit_count;
GBLREF jnlpool_addrs		jnlpool;
GBLREF uint4			process_id;
GBLREF uint4			crit_deadlock_check_cycle;
GBLREF uint4			dollar_tlevel;
GBLREF unsigned int		t_tries;
#if defined(UNIX) && defined(DEBUG)
GBLREF jnl_gbls_t		jgbl;
#endif

error_def(ERR_MUTEXRELEASED);

/* Return number of regions (including jnlpool dummy region) if have or are aquiring crit or in_wtstart
 * ** NOTE **  This routine is called from signal handlers and is thus called asynchronously.
 * If CRIT_IN_COMMIT bit is set, we check if in middle of commit (PHASE1 inside crit or PHASE2 outside crit) on some region.
 * If CRIT_RELEASE bit is set, we release crit on region(s) that:
 *   1)  we hold crit on (neither CRIT_IN_COMMIT NOR CRIT_TRANS_NO_REG is specified)
 *   2)  are part of the current transactions except those regions that are marked as being valid
 *       to have crit in by virtue of their crit_check_cycle value is the same as crit_deadlock_check_cycle.
 * Note: CRIT_RELEASE implies CRIT_ALL_REGIONS
 * If CRIT_ALL_REGIONS bit is set, go through the entire list of regions
 */
uint4 have_crit(uint4 crit_state)
{
	gd_region	*r_top, *r_local;
	gd_addr		*addr_ptr;
	sgmnt_addrs	*csa;
	uint4		crit_reg_cnt = 0;

	/* in order to proper release the necessary regions, CRIT_RELEASE implies going through all the regions */
	if (crit_state & CRIT_RELEASE)
	{
		UNIX_ONLY(assert(!jgbl.onlnrlbk)); /* should not request crit to be released if online rollback */
		crit_state |= CRIT_ALL_REGIONS;
	}
	if (0 != crit_count)
	{
		crit_reg_cnt++;
		if (0 == (crit_state & CRIT_ALL_REGIONS))
			return crit_reg_cnt;
	}
	for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
	{
		for (r_local = addr_ptr->regions, r_top = r_local + addr_ptr->n_regions; r_local < r_top; r_local++)
		{
			if (r_local->open && !r_local->was_open)
			{
				csa = &FILE_INFO(r_local)->s_addrs;
				if (NULL != csa)
				{
					if (csa->now_crit)
					{
						crit_reg_cnt++;
						/* It is possible that if DSE has done a CRIT REMOVE and stolen our crit, it
						 * could be given to someone else which would cause this test to fail. The
						 * current thinking is that the state DSE put this process is no longer viable
						 * and it should die at the earliest opportunity, there being no way to know if
						 * that is what happened anyway.
						 */
						if (csa->nl->in_crit != process_id)
							GTMASSERT;
						/* If we are releasing (all) regions with critical section or if special
						 * TP case, release if the cycle number doesn't match meaning this is a
						 * region we should not hold crit in (even if it is part of tp_reg_list).
						 */
						if ((0 != (crit_state & CRIT_RELEASE)) &&
						    (0 == (crit_state & CRIT_NOT_TRANS_REG) ||
						     crit_deadlock_check_cycle != csa->crit_check_cycle))
						{
							assert(FALSE);
							assert(!csa->hold_onto_crit);
							rel_crit(r_local);
							send_msg(VARLSTCNT(8) ERR_MUTEXRELEASED, 6,
								 process_id, process_id,  DB_LEN_STR(r_local),
								 dollar_tlevel, t_tries);
						}
						if (0 == (crit_state & CRIT_ALL_REGIONS))
							return crit_reg_cnt;
					}
					/* In Commit-crit is defined as the time since when early_tn is 1 + curr_tn upto when
					 * t_commit_crit is set to FALSE. Note that the first check should be done only if we
					 * hold crit as otherwise we could see inconsistent values.
					 */
					if ((crit_state & CRIT_IN_COMMIT)
						&& (csa->now_crit && (csa->ti->early_tn != csa->ti->curr_tn) || csa->t_commit_crit))
					{
						crit_reg_cnt++;
						if (0 == (crit_state & CRIT_ALL_REGIONS))
							return crit_reg_cnt;
					}
					if ((crit_state & CRIT_IN_WTSTART) && csa->in_wtstart)
					{
						crit_reg_cnt++;
						if (0 == (crit_state & CRIT_ALL_REGIONS))
							return crit_reg_cnt;
					}
				}
			}
		}
	}
	if (NULL != jnlpool.jnlpool_ctl)
	{
		csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs;
		if (NULL != csa && csa->now_crit)
		{
			crit_reg_cnt++;
			if (0 != (crit_state & CRIT_RELEASE))
			{
				assert(!csa->hold_onto_crit);
				rel_lock(jnlpool.jnlpool_dummy_reg);
			}
		}
	}
	return crit_reg_cnt;
}
