fis-gtm/sr_port/deferred_events.c

519 lines
18 KiB
C

/****************************************************************
* *
* Copyright 2001, 2011 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"
#ifdef VMS
#include "efn.h"
#include <ssdef.h>
#endif
#include "xfer_enum.h"
#include "tp_timeout.h"
#include "deferred_events.h"
#include "outofband.h"
#include "interlock.h"
#include "lockconst.h"
#include "add_inter.h"
#include "op.h"
#include "iott_wrterr.h"
#ifdef DEBUG_DEFERRED
#include "gtm_stdio.h"
#endif
#include "fix_xfer_entry.h"
/* =============================================================================
* EXTERNAL VARIABLES
* =============================================================================
*/
/* The transfer table */
GBLREF xfer_entry_t xfer_table[];
/* M Profiling active */
GBLREF boolean_t is_tracing_on;
/* Marks sensitive database operations */
GBLREF volatile int4 fast_lock_count;
#if defined(VMS)
GBLREF volatile short num_deferred;
#else
GBLREF volatile int4 num_deferred;
#endif
GBLREF volatile int4 ctrap_action_is, outofband;
/* =============================================================================
* FILE-SCOPE VARIABLES
* =============================================================================
*/
/* ------------------------------------------------------------------
* Declared volatile because accesses occur both in main thread
* and (possibly multiple) interrupts levels.
* ------------------------------------------------------------------
*/
/* Holds count of events logged of each type.
* Cleared when table is reset.
* Location zero (== no_event) is not used.
*/
/* INCR_CNT on VMS doesn't return the post-incremented value as is the
* case in Unix. But we don't need an interlocked add in VMS since we
* should be in an AST and AST's can't be nested (we assert to that effect
* in xfer_set_handlers). The macro INCR_CNT_SP accomplishes this task for us.
*/
#if defined(UNIX)
volatile int4 xfer_table_events[DEFERRED_EVENTS];
#define INCR_CNT_SP(X,Y) INCR_CNT(X,Y)
#elif defined(VMS)
volatile short xfer_table_events[DEFERRED_EVENTS];
#define INCR_CNT_SP(X,Y) (++*X)
#else
# error "Unsupported Platform"
#endif
GBLREF global_latch_t defer_latch;
/* -------------------------------------------------------
* Act only on first recieved.
* -------------------------------------------------------
*/
GBLDEF volatile int4 first_event = no_event;
error_def(ERR_DEFEREVENT);
/* =============================================================================
* EXPORTED FUNCTIONS
* =============================================================================
*/
/* ------------------------------------------------------------------
* *** INTERRUPT HANDLER ***
* Sets up transfer table changes needed for:
* - Synchronous handling of asynchronous events.
* - Single-stepping and breakpoints
* Note:
* - Call here from a routine specific to each event type.
* - Pass in a single value to pass on to xfer_table set function
* for that type. Calling routine should record any other event
* info, if needed, in volatile global variables.
* - If this is first event logged, will call back to the function
* provided and pass along parameter.
* Future:
* - mdb_condition_handler does not call here -- should change it.
* - Ditto for routines related to zbreak and zstep.
* - Should put handler prototypes in a header file & include it here,
* if can use with some way to ensure type checking.
* - A higher-level interface (e.g. change sets) might be better.
* ------------------------------------------------------------------
*/
boolean_t xfer_set_handlers(int4 event_type, void (*set_fn)(int4 param), int4 param_val)
{
boolean_t is_first_event = FALSE;
/* ------------------------------------------------------------
* Keep track of what event types have come in.
* - Get and set value atomically in case of concurrent
* events and/or resetting while setting.
* ------------------------------------------------------------------
* Use interlocked operations to prevent races between set and reset,
* and to avoid missing overlapping sets.
* On HP:
* OK only if there's no a risk a conflicting operation is
* in progress (can deadlock).
* On all platforms:
* Don't want I/O from a sensitive area.
* Avoid both by testing fast_lock_count, and doing interlocks and
* I/O only if it is non-zero. Can't be resetting then, so worst
* risk is missing an event when there's already one happening.
* ------------------------------------------------------------------
*/
VMS_ONLY(assert(lib$ast_in_prog()));
if (fast_lock_count == 0)
{
#ifdef DEBUG_DEFERRED
(void) FPRINTF(stderr,
"\nBefore interlocked operations: "
"xfer_table_events[%d]=%d, "
"first_event=%s, "
"num_deferred=%d\n",
event_type,
xfer_table_events[event_type],
(is_first_event?"TRUE":"FALSE"),
num_deferred);
#endif
if (1 == INCR_CNT_SP(&xfer_table_events[event_type], &defer_latch))
{
/* Concurrent events can collide here, too */
is_first_event = (1 == INCR_CNT_SP(&num_deferred, &defer_latch));
}
#ifdef DEBUG_DEFERRED
(void) FPRINTF(stderr,
"\nAfter interlocked operations: "
"xfer_table_events[%d]=%d, "
"first_event=%s, "
"num_deferred=%d\n",
event_type,xfer_table_events[event_type],
(is_first_event?"TRUE":"FALSE"),
num_deferred);
#endif
} else if (1 == ++xfer_table_events[event_type])
is_first_event = (1 == ++num_deferred);
if (is_first_event)
{
first_event = event_type;
#ifdef DEBUG_DEFERRED
if (0 != fast_lock_count)
{
(void) FPRINTF(stderr,
"Setting xfer_table for event type %d.\n",
event_type);
}
#endif
/* -------------------------------------------------------
* If table changed, it was not synchronized.
* (Assumes these entries are all that would be changed)
* Note asserts bypassed for Itanium due to nature of the
* fixed up addresses making direct comparisions non-trivial.
* --------------------------------------------------------
*/
#ifndef __ia64
assert((xfer_table[xf_linefetch] == op_linefetch) ||
(xfer_table[xf_linefetch] == op_zstepfetch) ||
(xfer_table[xf_linefetch] == op_zst_fet_over) ||
(xfer_table[xf_linefetch] == op_mproflinefetch));
assert((xfer_table[xf_linestart] == op_linestart) ||
(xfer_table[xf_linestart] == op_zstepstart) ||
(xfer_table[xf_linestart] == op_zst_st_over) ||
(xfer_table[xf_linestart] == op_mproflinestart));
assert((xfer_table[xf_zbfetch] == op_zbfetch) ||
(xfer_table[xf_zbfetch] == op_zstzb_fet_over) ||
(xfer_table[xf_zbfetch] == op_zstzbfetch));
assert((xfer_table[xf_zbstart] == op_zbstart) ||
(xfer_table[xf_zbstart] == op_zstzb_st_over) ||
(xfer_table[xf_zbstart] == op_zstzbstart));
assert((xfer_table[xf_forchk1] == op_forchk1) ||
(xfer_table[xf_forchk1] == op_mprofforchk1));
assert((xfer_table[xf_forloop] == op_forloop));
assert(xfer_table[xf_ret] == opp_ret ||
xfer_table[xf_ret] == opp_zst_over_ret ||
xfer_table[xf_ret] == opp_zstepret);
assert(xfer_table[xf_retarg] == op_retarg ||
xfer_table[xf_retarg] == opp_zst_over_retarg ||
xfer_table[xf_retarg] == opp_zstepretarg);
#endif /* !ia64 */
/* -----------------------------------------------
* Now call the specified set function to swap in
* the desired handlers (and set flags or whatever).
* -----------------------------------------------
*/
set_fn(param_val);
}
#ifdef DEBUG_DEFERRED
else if (0 != fast_lock_count)
{
(void) FPRINTF(stderr,
"\n---Multiple deferred events---\n"
"Event type %d occurred while type %d was pending\n",
event_type,
first_event);
}
#endif
assert(no_event != first_event);
return is_first_event;
}
/* ------------------------------------------------------------------
* Reset the transfer table only if current event type
* - Needed for abort before action, e.g., a tp timeout
* that happened just before commit was too late to be aborted and
* so must be cleared. In a case like this, reset should happen
* only if the event type attempting the clear is the type
* that caused the change.
* - Other resets should be unconditional, in case there are
* unidentified control paths (e.g. exit paths) that cause
* resets when they did not set.
* ------------------------------------------------------------------
*/
boolean_t xfer_reset_if_setter(int4 event_type)
{
if (event_type == first_event)
{
/* Still have to synchronize the same way... */
if (TRUE == xfer_reset_handlers(event_type))
{ /* *********************************
* Check for and activate any
* other pending events before
* returning success:
* *********************************
*/
/* (Not implemented) */
return TRUE;
} else
{ /* ---------------------------------
* Would require interleaved resets
* to get here, e.g. due to
* rts_error from interrupt level.
* ---------------------------------
*/
assert(FALSE);
return FALSE;
}
} else
return FALSE;
}
/* ------------------------------------------------------------------
* Reset transfer table to normal settings.
*
* - Intent: Put back all state that was or could have been changed
* due to prior deferral(s).
* - Would be easier to implement this assumption if this routine
* were changed to delegate responsibility as does the
* corresponding set routine.
* - Note that all events are reenabled before user's handler
* would be executed (assuming one is appropriate for this event
* and has been specified)
* => It's possible to have handler-in-handler execution.
* => If no handler executed, would lose other deferred events due
* to reset of all pending.
* - If M profiling is active, some entries should be set to the
* op_mprof* routines.
* - Return value indicates whether reset type matches set type.
* If it does not, this indicates an "abnormal" path.
* - Should still reset the table in this case.
* - BUT: Consider also calling a reset routine for all setters
* that have been logged, to allow them to reset themselves,
* (for example, to reset TP timer & flags, or anything else
* that could cause unintended effects if left set after
* deferred events have been cleared).
* - May need to update behavior to ensure it doesn't miss a
* critical event between registration of first event
* and clearing of all events. This seems problematic only if
* the following are true:
* - Two events are deferred at one time (call them A and B).
* - An M exception handler (ZTRAP or device) is required to
* execute due to B and perform action X.
* - Either no handler executes due to A, or the handler that
* does execute does not perform action X in response to B
* (this includes the possibility of performing X but not
* as needed by B, e.g. perhaps it should happen for both
* A and B but only happens for A).
* Seems like most or all of these can be addressed by carefully
* specifying coding requirements on M handlers.
* ------------------------------------------------------------------
*/
boolean_t xfer_reset_handlers(int4 event_type)
{
int4 e_type;
boolean_t reset_type_is_set_type;
int4 status;
int e, ei, e_tot=0;
/* ------------------------------------------------------------------
* Note: If reset routine can preempt path from handler to
* set routine (e.g. clearing event before acting on it),
* these assertions can fail.
* Should not happen in current design.
* ------------------------------------------------------------------
*/
assert(0 < num_deferred);
assert(0 < xfer_table_events[event_type]);
if (is_tracing_on)
{
FIX_XFER_ENTRY(xf_linefetch, op_mproflinefetch);
FIX_XFER_ENTRY(xf_linestart, op_mproflinestart);
FIX_XFER_ENTRY(xf_forchk1, op_mprofforchk1);
} else
{
FIX_XFER_ENTRY(xf_linefetch, op_linefetch);
FIX_XFER_ENTRY(xf_linestart, op_linestart);
FIX_XFER_ENTRY(xf_forchk1, op_forchk1);
}
FIX_XFER_ENTRY(xf_forloop, op_forloop);
FIX_XFER_ENTRY(xf_zbfetch, op_zbfetch);
FIX_XFER_ENTRY(xf_zbstart, op_zbstart);
FIX_XFER_ENTRY(xf_ret, opp_ret);
FIX_XFER_ENTRY(xf_retarg, op_retarg);
#ifdef DEBUG_DEFERRED
(void) FPRINTF(stderr,"Reset xfer_table for event type %d.\n",event_type);
#endif
reset_type_is_set_type = (event_type == first_event);
#ifdef DEBUG
if (!reset_type_is_set_type)
{
rts_error(VARLSTCNT(4) ERR_DEFEREVENT,
2,
event_type,
first_event);
}
#endif
#ifdef DEBUG_DEFERRED
/*--------------------------------------------=---------------------------------
* Note: concurrent modification of array elements means events that occur
* during this section will cause inconsistent totals.
*-----------------------------------------------------------------------------
*/
for (ei=no_event; ei<DEFERRED_EVENTS; ei++)
{
e_tot += xfer_table_events[ei];
}
if (e_tot >1)
{
(void) FPRINTF(stderr,"Event Log:\n");
for (ei=no_event; ei<DEFERRED_EVENTS; ei++)
{
(void) FPRINTF(stderr,
" Event type %d: count was %d.\n",
ei,
xfer_table_events[ei]);
}
}
#endif
/* -------------------------------------------------------------------------
* Kluge(?): set all locations to nonzero value to
* prevent interleaving with reset activities.
*
* Would be better to aswp with 0:
* - Won't lose any new events that way.
* -------------------------------------------------------------------------
*/
for (e_type = 1; DEFERRED_EVENTS > e_type; e_type++)
{
xfer_table_events[e_type] = 1;
}
/* -------------------------------------------------------------------------
* Reset external event modules that need it.
* (Should do this in a more modular fashion.)
* None
* -------------------------------------------------------------------------
*/
/* --------------------------------------------
* Reset private variables.
* --------------------------------------------
*/
first_event = no_event;
num_deferred = 0;
ctrap_action_is = 0;
outofband = 0;
VMS_ONLY(
status = sys$clref(efn_outofband);
assert(SS$_WASSET == status);
if ((SS$_WASSET != status) && (SS$_WASCLR != status))
GTMASSERT;
)
/* ******************************************************************
* There is a race here:
* If a new event interrupts after previous line and before
* corresponding assignment in next loop, it will be missed.
* For most events, we're going to an M handler anyway, so it won't
* matter (assuming the handler would handle all pending events).
* But if not going to an M handler (e.g. if resetting zbreak/zstep),
* could miss another event.
*
* Better (to avoid missing any events):
* aswp xfer_table_events elements (as described above), and
* check here if still zero. If not, must have missed that event
* since aswp, possibly before num_deferred was reset => never set
* xfer_table => should do that now.
* If more than one is nonzero, choose first arbitrarily
* unless first_event is now set -- unless it is, we've lost track of
* which event was first.
* ******************************************************************
*/
/* Clear to allow new events to be reset only after we're all done. */
for (e_type = 1; DEFERRED_EVENTS > e_type; e_type++)
{
xfer_table_events[e_type] = FALSE;
}
return reset_type_is_set_type;
}
/* ------------------------------------------------------------------
* Perform action corresponding to the first async event that
* was logged.
* ------------------------------------------------------------------
*/
void async_action(bool lnfetch_or_start)
{
/* Double-check that we should be here: */
assert(0 < num_deferred);
switch(first_event)
{
case (outofband_event):
if (0 == outofband)
{ /* This function can be invoked only by a op_*intrrpt* transfer table function. Those transfer table
* functions should be active only for a short duration between the occurrence of an outofband event
* and the handling of it at a logical boundary (next M-line). We dont expect to be running with
* those transfer table functions for more than one M-line. If "outofband" is set to 0, the call to
* "outofband_action" below will do nothing and we will end up running with the op_*intrrpt* transfer
* table functions indefinitely. In this case M-FOR loops are known to return incorrect results which
* might lead to application integrity issues. It is therefore considered safer to GTMASSERT as we
* will at least have the core for analysis.
*/
GTMASSERT;
}
outofband_action(lnfetch_or_start);
break;
case (tt_write_error_event):
UNIX_ONLY(
xfer_reset_if_setter(tt_write_error_event);
iott_wrterr();
)
/* VMS tt error processing is done in op_*intrrpt */
break;
case (network_error_event):
/* -------------------------------------------------------
* Network error not implemented here yet. Need to move
* from mdb_condition_handler after review.
* -------------------------------------------------------
*/
case (zstp_or_zbrk_event):
/* -------------------------------------------------------
* ZStep/Zbreak events not implemented here yet. Need to
* move here after review.
* -------------------------------------------------------
*/
default:
GTMASSERT; /* see above GTMASSERT for comment as to why this is needed */
}
}
/* ------------------------------------------------------------------
* Indicate whether an xfer_table change is pending.
* Only works for changes made using routines in this module
* (need to rework others, too).
*
* Might not be needed.
* ------------------------------------------------------------------
*/
boolean_t xfer_table_changed(void)
{
return (0 != num_deferred);
}