740 lines
32 KiB
C
740 lines
32 KiB
C
/****************************************************************
|
|
* *
|
|
* 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_string.h"
|
|
|
|
#ifdef VMS
|
|
#include <descrip.h>
|
|
#endif
|
|
|
|
#include "gdsroot.h"
|
|
#include "gdsblk.h"
|
|
#include "gdskill.h"
|
|
#include "gtm_facility.h"
|
|
#include "fileinfo.h"
|
|
#include "gdsbt.h"
|
|
#include "gdsfhead.h"
|
|
#include "filestruct.h"
|
|
#include "gdscc.h"
|
|
#include "cdb_sc.h"
|
|
#include "error.h"
|
|
#include "iosp.h" /* for declaration of SS_NORMAL */
|
|
#include "jnl.h"
|
|
#include "rtnhdr.h"
|
|
#include "mv_stent.h"
|
|
#include "stack_frame.h"
|
|
#include "hashtab_int4.h" /* needed for tp.h */
|
|
#include "buddy_list.h" /* needed for tp.h */
|
|
#include "tp.h"
|
|
#include "tp_frame.h"
|
|
#include "gtm_stdio.h"
|
|
#include "gtm_stdlib.h" /* for ATOI */
|
|
#include "send_msg.h"
|
|
#include "op.h"
|
|
#include "io.h"
|
|
#include "targ_alloc.h"
|
|
#include "getzposition.h"
|
|
#include "wcs_recover.h"
|
|
#include "tp_unwind.h"
|
|
#include "wcs_backoff.h"
|
|
#include "rel_quant.h"
|
|
#include "wcs_mm_recover.h"
|
|
#include "tp_restart.h"
|
|
#include "repl_msg.h" /* for gtmsource.h */
|
|
#include "gtmsource.h" /* for jnlpool_addrs structure definition */
|
|
#include "wbox_test_init.h"
|
|
#include "gtmimagename.h"
|
|
#include "have_crit.h"
|
|
#ifdef GTM_TRIGGER
|
|
#include "gv_trigger.h"
|
|
#include "gtm_trigger.h"
|
|
#endif
|
|
#ifdef ENABLE_EXTENDED_RESTART_TRACE_HIST
|
|
#include "caller_id.h"
|
|
#endif
|
|
|
|
GBLDEF trans_num tp_fail_histtn[CDB_MAX_TRIES], tp_fail_bttn[CDB_MAX_TRIES];
|
|
GBLDEF int4 tp_fail_n, tp_fail_level;
|
|
GBLDEF int4 n_pvtmods, n_blkmods;
|
|
GBLDEF gv_namehead *tp_fail_hist[CDB_MAX_TRIES];
|
|
GBLDEF block_id t_fail_hist_blk[CDB_MAX_TRIES];
|
|
GBLDEF gd_region *tp_fail_hist_reg[CDB_MAX_TRIES];
|
|
|
|
GBLREF uint4 dollar_tlevel;
|
|
GBLREF uint4 dollar_trestart;
|
|
GBLREF int dollar_truth;
|
|
GBLREF mval dollar_zgbldir;
|
|
GBLREF gd_addr *gd_header;
|
|
GBLREF gv_key *gv_currkey;
|
|
GBLREF gv_namehead *gv_target;
|
|
GBLREF stack_frame *frame_pointer;
|
|
GBLREF tp_frame *tp_pointer;
|
|
GBLREF sgm_info *sgm_info_ptr;
|
|
GBLREF tp_region *tp_reg_list; /* Chained list of regions used in this transaction not cleared on tp_restart */
|
|
GBLREF mv_stent *mv_chain;
|
|
GBLREF unsigned char *msp, *stackbase, *stacktop, t_fail_hist[CDB_MAX_TRIES];
|
|
GBLREF sgm_info *first_sgm_info;
|
|
GBLREF unsigned int t_tries;
|
|
GBLREF int process_id;
|
|
GBLREF gd_region *gv_cur_region;
|
|
GBLREF jnlpool_addrs jnlpool;
|
|
GBLREF bool caller_id_flag;
|
|
GBLREF unsigned char *tpstackbase, *tpstacktop;
|
|
GBLREF trans_num local_tn; /* transaction number for THIS PROCESS */
|
|
GBLREF sgmnt_addrs *cs_addrs;
|
|
GBLREF sgmnt_data *cs_data;
|
|
GBLREF symval *curr_symval;
|
|
GBLREF trans_num tstart_local_tn; /* copy of global variable "local_tn" at op_tstart time */
|
|
#ifdef VMS
|
|
GBLREF struct chf$signal_array *tp_restart_fail_sig;
|
|
GBLREF boolean_t tp_restart_fail_sig_used;
|
|
#else
|
|
GBLREF jnl_gbls_t jgbl;
|
|
GBLREF sgmnt_addrs *cs_addrs_list;
|
|
GBLREF boolean_t is_updproc;
|
|
#endif
|
|
#ifdef GTM_TRIGGER
|
|
GBLREF int tprestart_state; /* When triggers restart, multiple states possible. See tp_restart.h */
|
|
GBLREF mval dollar_ztwormhole; /* Previous value (mval) restored on restart */
|
|
GBLREF mval dollar_ztslate;
|
|
LITREF mval literal_null;
|
|
#endif
|
|
|
|
#ifdef UNIX
|
|
error_def(ERR_GVFAILCORE);
|
|
error_def(ERR_REPLONLNRLBK);
|
|
#endif
|
|
error_def(ERR_TLVLZERO);
|
|
error_def(ERR_TPFAIL);
|
|
error_def(ERR_TPLOCKRESTMAX);
|
|
error_def(ERR_TPRESTART);
|
|
error_def(ERR_TPRETRY);
|
|
error_def(ERR_TRESTLOC);
|
|
error_def(ERR_TRESTNOT);
|
|
|
|
#define GVNAME_UNKNOWN "*UNKNOWN"
|
|
|
|
static readonly char gvname_unknown[] = GVNAME_UNKNOWN;
|
|
static readonly int4 gvname_unknown_len = STR_LIT_LEN(GVNAME_UNKNOWN);
|
|
|
|
CONDITION_HANDLER(tp_restart_ch)
|
|
{
|
|
START_CH;
|
|
/* On Unix, there is only one set of the signal info and this error will handily replace it. For VMS,
|
|
* far more subterfuge is required. We will save the signal information and paramters and overlay the
|
|
* TPRETRY signal information with it so that the signal will be handled properly. Note also that since
|
|
* VMS does not support triggers, no special avoidance of the below needs to occur when we are dealing with
|
|
* a trigger unwind initiated rethrow.
|
|
*/
|
|
# ifdef VMS
|
|
assert(TPRESTART_ARG_CNT >= sig->chf$is_sig_args);
|
|
if (NULL == tp_restart_fail_sig)
|
|
tp_restart_fail_sig = (struct chf$signal_array *)malloc((TPRESTART_ARG_CNT + 1) * SIZEOF(int));
|
|
memcpy(tp_restart_fail_sig, sig, (sig->chf$is_sig_args + 1) * SIZEOF(int));
|
|
assert(FALSE == tp_restart_fail_sig_used);
|
|
tp_restart_fail_sig_used = TRUE;
|
|
# endif
|
|
GTMTRIG_ONLY(DBGTRIGR((stderr, "tp_restart_ch: ERROR!! unwinding C frame to return. Error is %d, tprestart_state is %d\n",
|
|
arg, tprestart_state)));
|
|
UNWIND(NULL, NULL);
|
|
}
|
|
|
|
/* Note that adding a new rts_error in "tp_restart" might need a change to the INVOKE_RESTART macro in tp.h and
|
|
* TPRESTART_ARG_CNT in errorsp.h (sl_vvms). See comment in tp.h for INVOKE_RESTART macro for the details.
|
|
*/
|
|
int tp_restart(int newlevel, boolean_t handle_errors_internally)
|
|
{
|
|
unsigned char *cp;
|
|
short top;
|
|
unsigned int hist_index;
|
|
tp_frame *tf;
|
|
mv_stent *mvc;
|
|
tp_region *tr;
|
|
mval beganHere;
|
|
sgmnt_addrs *csa;
|
|
int4 num_closed = 0;
|
|
boolean_t tp_tend_status;
|
|
mstr gvname_mstr, reg_mstr;
|
|
gd_region *restart_reg, *reg;
|
|
int tprestart_rc;
|
|
enum cdb_sc status;
|
|
UNIX_ONLY(boolean_t issue_REPLONLNRLBK;)
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
tprestart_rc = 0;
|
|
/* Some callers of "tp_restart" want this function to return with an error code instead of issue an rts_error
|
|
* if there is an error inside tp_restart. The SIGNAL macro is supposed to reflect the error code in that
|
|
* case and the caller handles this error code accordingly after tp_restart returns. For those callers,
|
|
* establish the "tp_restart_ch" condition handler to catch all errors. For the remaining callers, any errors
|
|
* inside tp_restart will invoke whatever parent condition handler is active at that point.
|
|
*
|
|
* The reason why a few callers prefer this inside-tprestart error handling is because they are already a
|
|
* condition/error handler (e.g. mdb_condition_handler) when they invoke tp_restart and do not want another
|
|
* rts_error to happen inside tp_restart and trigger any other condition/error handlers that can alter the
|
|
* flow of control elsewhere until this condition handler returns.
|
|
*/
|
|
if (handle_errors_internally)
|
|
{ /* Currently, the only callers of tp_restart with handle_errors_internally set to TRUE are
|
|
* "mdb_condition_handler", "updproc.c" and "mupip_recover.c". All of those have SIGNAL set
|
|
* to ERR_TPRETRY so assert that. This is one way of protecting against a new caller of tp_restart
|
|
* inadvertently using a TRUE value for "handle_errors_internally". One reason for being paranoid
|
|
* about the TRUE usage is for example in gv_trigger.c, if tp_restart is incorrectly invoked with
|
|
* TRUE as the second paramter, it will result in indefinite number of cores on a broken database.
|
|
* In VMS, SIGNAL can be used only if we are inside a condition handler so do the assert only in Unix.
|
|
*/
|
|
UNIX_ONLY(assert(ERR_TPRETRY == SIGNAL));
|
|
ESTABLISH_RET(tp_restart_ch, tprestart_rc);
|
|
}
|
|
assert(1 == newlevel);
|
|
if (!dollar_tlevel)
|
|
{
|
|
rts_error(VARLSTCNT(1) ERR_TLVLZERO);
|
|
return 0; /* for the compiler only -- never executed */
|
|
}
|
|
# ifdef DEBUG
|
|
if (TREF(tp_restart_dont_counts) >= dollar_trestart)
|
|
TREF(tp_restart_dont_counts) = dollar_trestart;
|
|
# endif
|
|
|
|
# ifdef GTM_TRIGGER
|
|
DBGTRIGR((stderr, "tp_restart: Entry state: %d\n", tprestart_state));
|
|
if (TPRESTART_STATE_NORMAL == tprestart_state)
|
|
{ /* Only do if a normal invocation - otherwise we've already done this code for this TP restart */
|
|
# endif
|
|
/* Increment restart counts for each region in this transaction */
|
|
for (tr = tp_reg_list; NULL != tr; tr = tr->fPtr)
|
|
{
|
|
reg = tr->reg;
|
|
if (reg->open)
|
|
{
|
|
csa = &FILE_INFO(reg)->s_addrs;
|
|
switch (dollar_trestart)
|
|
{
|
|
case 0:
|
|
INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_tot_retries_0, 1);
|
|
break;
|
|
case 1:
|
|
INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_tot_retries_1, 1);
|
|
break;
|
|
case 2:
|
|
INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_tot_retries_2, 1);
|
|
break;
|
|
case 3:
|
|
INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_tot_retries_3, 1);
|
|
break;
|
|
default:
|
|
INCR_GVSTATS_COUNTER(csa, csa->nl, n_tp_tot_retries_4, 1);
|
|
break;
|
|
}
|
|
} else
|
|
{
|
|
assert(cdb_sc_needcrit == t_fail_hist[t_tries]);
|
|
assert(!num_closed); /* we can have at the most 1 region not opened in the whole tp_reg_list */
|
|
num_closed++;
|
|
}
|
|
}
|
|
status = t_fail_hist[t_tries];
|
|
/* The only reason online rollback ends up restarting is if some process set wc_blocked outside crit or the prior
|
|
* transaction had commit errors and secshr_db_clnup set wc_blocked to TRUE. But, that's possible only if white box
|
|
* test cases to induce Phase 1 and Phase 2 commit errors are set. So, assert accordingly
|
|
*/
|
|
UNIX_ONLY(assert(!jgbl.onlnrlbk || WB_COMMIT_ERR_ENABLED));
|
|
if (TREF(tprestart_syslog_delta) && (((TREF(tp_restart_count))++ < TREF(tprestart_syslog_limit))
|
|
|| (0 == ((TREF(tp_restart_count) - TREF(tprestart_syslog_limit)) % TREF(tprestart_syslog_delta)))))
|
|
{
|
|
if (NULL != tp_fail_hist[t_tries])
|
|
gvname_mstr = tp_fail_hist[t_tries]->gvname.var_name;
|
|
else
|
|
{
|
|
gvname_mstr.addr = (char *)gvname_unknown;
|
|
gvname_mstr.len = gvname_unknown_len;
|
|
}
|
|
caller_id_flag = FALSE; /* don't want caller_id in the operator log */
|
|
assert(0 == cdb_sc_normal);
|
|
if (cdb_sc_normal == status)
|
|
t_fail_hist[t_tries] = '0'; /* temporarily reset just for pretty printing */
|
|
restart_reg = tp_fail_hist_reg[t_tries];
|
|
if (NULL != restart_reg)
|
|
{
|
|
reg_mstr.len = restart_reg->dyn.addr->fname_len;
|
|
reg_mstr.addr = (char *)restart_reg->dyn.addr->fname;
|
|
} else
|
|
{
|
|
reg_mstr.len = 0;
|
|
reg_mstr.addr = NULL;
|
|
}
|
|
if (cdb_sc_blkmod != status)
|
|
{
|
|
send_msg(VARLSTCNT(16) ERR_TPRESTART, 14, reg_mstr.len, reg_mstr.addr,
|
|
t_tries + 1, t_fail_hist, t_fail_hist_blk[t_tries], gvname_mstr.len, gvname_mstr.addr,
|
|
0, 0, 0, tp_blkmod_nomod,
|
|
(NULL != sgm_info_ptr) ? sgm_info_ptr->num_of_blks : 0,
|
|
(NULL != sgm_info_ptr) ? sgm_info_ptr->cw_set_depth : 0, &local_tn);
|
|
} else
|
|
{
|
|
send_msg(VARLSTCNT(16) ERR_TPRESTART, 14, reg_mstr.len, reg_mstr.addr,
|
|
t_tries + 1, t_fail_hist, t_fail_hist_blk[t_tries], gvname_mstr.len, gvname_mstr.addr,
|
|
n_pvtmods, n_blkmods, tp_fail_level, tp_fail_n,
|
|
sgm_info_ptr->num_of_blks,
|
|
sgm_info_ptr->cw_set_depth, &local_tn);
|
|
}
|
|
tp_fail_hist_reg[t_tries] = NULL;
|
|
tp_fail_hist[t_tries] = NULL;
|
|
if ('0' == t_fail_hist[t_tries])
|
|
t_fail_hist[t_tries] = cdb_sc_normal; /* get back to where it was */
|
|
caller_id_flag = TRUE;
|
|
n_pvtmods = n_blkmods = 0;
|
|
}
|
|
/* Should never come here with a normal restart code */
|
|
assert(cdb_sc_normal != status);
|
|
# ifdef DEBUG
|
|
TAREF1(tp_restart_failhist_arry, (TREF(tp_restart_failhist_indx))++) = status;
|
|
if (FAIL_HIST_ARRAY_SIZE <= TREF(tp_restart_failhist_indx))
|
|
TREF(tp_restart_failhist_indx) = 0;
|
|
TRACE_TRANS_RESTART(status);
|
|
# endif
|
|
/* the following code is similar, but not identical, to code in t_retry,
|
|
* which should also be maintained in parallel
|
|
*/
|
|
switch (status)
|
|
{
|
|
case cdb_sc_helpedout:
|
|
csa = sgm_info_ptr->tp_csa;
|
|
if (dba_bg == csa->hdr->acc_meth)
|
|
{
|
|
if (!csa->now_crit)
|
|
{ /* The following grab/rel crit logic is purely to ensure that wcs_recover
|
|
* gets called if needed. This is because we saw wc_blocked to be TRUE in
|
|
* tp_tend and decided to restart.
|
|
*/
|
|
assert(!csa->hold_onto_crit);
|
|
grab_crit(sgm_info_ptr->gv_cur_region);
|
|
rel_crit(sgm_info_ptr->gv_cur_region);
|
|
} else
|
|
{ /* Some non-crit holding process set wc_blocked to TRUE causing us to
|
|
* restart even though we held crit. Most likely phase2 commit or a
|
|
* process in wcs_wtstart encountered an error. In any case, need to run
|
|
* cache-recovery to fix the shared memory structures. Since we hold crit,
|
|
* so no need to grab/rel crit. Call wcs_recover right away.
|
|
*/
|
|
assert(!csa->hold_onto_crit UNIX_ONLY(|| jgbl.onlnrlbk));
|
|
DEBUG_ONLY(TREF(ok_to_call_wcs_recover) = TRUE);
|
|
wcs_recover(sgm_info_ptr->gv_cur_region);
|
|
DEBUG_ONLY(TREF(ok_to_call_wcs_recover) = FALSE);
|
|
}
|
|
} else
|
|
{
|
|
assert(dba_mm == csa->hdr->acc_meth);
|
|
wcs_recover(sgm_info_ptr->gv_cur_region);
|
|
}
|
|
DEBUG_ONLY((TREF(tp_restart_dont_counts))++);
|
|
if (CDB_STAGNATE > t_tries)
|
|
{
|
|
t_tries++;
|
|
break;
|
|
}
|
|
/* WARNING - fallthrough !!! */
|
|
case cdb_sc_needcrit:
|
|
case cdb_sc_needlock:
|
|
/* Here when a final (4th) attempt has failed with a need for crit in some routine. The
|
|
* assumption is that the previous attempt failed somewhere before transaction end
|
|
* therefore tp_reg_list did not have a complete list of regions necessary to complete the
|
|
* transaction and therefore not all the regions have been locked down. The new region (by
|
|
* virtue of it having now been referenced) has been added to tp_reg_list so all we need
|
|
* now is a retry.
|
|
*/
|
|
assert(CDB_STAGNATE == t_tries);
|
|
for (tr = tp_reg_list; NULL != tr; tr = tr->fPtr)
|
|
{ /* regions might not have been opened if we t_retried in gvcst_init(). dont
|
|
* rel_crit in that case.
|
|
*/
|
|
reg = tr->reg;
|
|
if (reg->open)
|
|
{
|
|
DEBUG_ONLY(csa = &FILE_INFO(reg)->s_addrs;)
|
|
assert(!csa->hold_onto_crit);
|
|
rel_crit(reg); /* to ensure deadlock safe order, release all regions
|
|
* before retry */
|
|
}
|
|
}
|
|
DEBUG_ONLY(
|
|
/* The journal pool crit lock is currently obtained only inside commit logic at
|
|
* which point we will never signal a cdb_sc_needcrit or cdb_sc_needlock restart
|
|
* code. So no need to verify if we need to release crit there. Assert this though.
|
|
*/
|
|
if ((NULL != jnlpool.jnlpool_dummy_reg) && jnlpool.jnlpool_dummy_reg->open)
|
|
{
|
|
csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs;
|
|
assert(!csa->now_crit);
|
|
}
|
|
)
|
|
/* If retry due to M-locks, sleep so needed locks have a chance to get released */
|
|
if (cdb_sc_needlock == status)
|
|
{ /* Since we are in the final retry and holding crit on at least one database, we want
|
|
* to limit the # of times this transaction can restart due to a failed M-lock
|
|
* attempt as a restart entails wasted work while holding crit on the db and
|
|
* preventing others from accessing the same.
|
|
*/
|
|
if (TREF(tp_restart_needlock_tn) != tstart_local_tn)
|
|
{
|
|
TREF(tp_restart_needlock_cnt) = 0; /* Restart counting */
|
|
TREF(tp_restart_needlock_tn) = tstart_local_tn;
|
|
}
|
|
(TREF(tp_restart_needlock_cnt))++;
|
|
assert(MAX_TP_FINAL_RETRY_MLOCKRESTART_CNT >= TREF(tp_restart_needlock_cnt));
|
|
if (MAX_TP_FINAL_RETRY_MLOCKRESTART_CNT <= TREF(tp_restart_needlock_cnt))
|
|
rts_error(VARLSTCNT(3) ERR_TPLOCKRESTMAX, 1, MAX_TP_FINAL_RETRY_MLOCKRESTART_CNT);
|
|
wcs_backoff(dollar_trestart * TP_DEADLOCK_FACTOR);
|
|
}
|
|
break;
|
|
/* Journaling might get turned off in the final retry INSIDE crit while trying to flush journal buffer or
|
|
* during extending the journal file (due to possible disk issues) in which case we will come here with
|
|
* t_tries = CDB_STAGNATE and failure status set to cdb_sc_jnlclose
|
|
*/
|
|
case cdb_sc_jnlclose:
|
|
/* cdb_sc_jnlstatemod is expected in final retry because csa->jnl_state is noted from csd->jnl_state only
|
|
* if they are different INSIDE crit. Therefore it is possible that in the final retry one might start with
|
|
* a stale value of csa->jnl_state which will be noticed only in t_end just before commit as a result of
|
|
* which we would restart. Such a restart is okay (instead of the checking for jnl state change during the
|
|
* beginning of final retry) since jnl state changes are considered infrequent that too in the final retry
|
|
*/
|
|
case cdb_sc_jnlstatemod:
|
|
# ifdef UNIX
|
|
/* cdb_sc_onln_rlbk[1,2] are possible in the final retry. See comment below that explains why. So, decrement
|
|
* t_tries to account for later increment
|
|
*/
|
|
case cdb_sc_onln_rlbk1:
|
|
case cdb_sc_onln_rlbk2:
|
|
# endif
|
|
case cdb_sc_optrestart:
|
|
if (CDB_STAGNATE <= t_tries)
|
|
{
|
|
t_tries--;
|
|
DEBUG_ONLY((TREF(tp_restart_dont_counts))++);
|
|
}
|
|
/* fall through */
|
|
default:
|
|
if (CDB_STAGNATE < ++t_tries)
|
|
{
|
|
hist_index = t_tries;
|
|
t_tries = 0;
|
|
assert(0 != have_crit(CRIT_HAVE_ANY_REG)); /* we should still be holding crit */
|
|
assert(gtm_white_box_test_case_enabled
|
|
&& (WBTEST_TP_HIST_CDB_SC_BLKMOD == gtm_white_box_test_case_number));
|
|
# ifdef UNIX
|
|
send_msg(VARLSTCNT(5) ERR_TPFAIL, 2, hist_index, t_fail_hist, ERR_GVFAILCORE);
|
|
/* Generate core only if not triggering this codepath using white-box tests */
|
|
DEBUG_ONLY(
|
|
if (!gtm_white_box_test_case_enabled
|
|
|| (WBTEST_TP_HIST_CDB_SC_BLKMOD != gtm_white_box_test_case_number))
|
|
)
|
|
gtm_fork_n_core();
|
|
# endif
|
|
VMS_ONLY(send_msg(VARLSTCNT(4) ERR_TPFAIL, 2, hist_index, t_fail_hist));
|
|
rts_error(VARLSTCNT(4) ERR_TPFAIL, 2, hist_index, t_fail_hist);
|
|
return 0; /* for the compiler only -- never executed */
|
|
} else
|
|
{
|
|
/* Yield the CPU so that the restarting process does not block the crit holder
|
|
* and/or other processes (referencing potentially non-intersecting database
|
|
* regions) in case they are waiting for the CPU. This is done using the
|
|
* rel_quant function.
|
|
* As of this writing, this operates only between the 2nd and 3rd tries;
|
|
* The 2nd is fast with the assumption of coincidental conflict in an attempt
|
|
* to take advantage of the buffer state created by the 1st try.
|
|
* The next to last try is not followed by a rel_quant as it may leave the buffers
|
|
* locked, to reduce live lock and deadlock issues.
|
|
* With only 4 tries that leaves only the "middle" for rel_quant.
|
|
*/
|
|
if ((CDB_STAGNATE - 1) == t_tries)
|
|
rel_quant();
|
|
}
|
|
}
|
|
if ((CDB_STAGNATE <= t_tries))
|
|
{ /* If there are any regions that haven't yet been opened, open them before attempting for crit on
|
|
* all. Since we don't hold any crit locks now, we can rest assured this cannot cause a deadlock.
|
|
* The only exception (to holding crit) currently is mupip journal rollback/recovery if online when
|
|
* we will be holding crit on all regions but in that case we should NOT have restarted in the first
|
|
* place as all the concurrent GT.M processes will be hung waiting for crit. The only exception is
|
|
* if some process set wc_blocked outside crit and we restarted to do cache recovery (which is asserted
|
|
* above)
|
|
*/
|
|
if (num_closed)
|
|
{
|
|
for (tr = tp_reg_list; NULL != tr; tr = tr->fPtr)
|
|
{ /* to open region use gv_init_reg() instead of gvcst_init() since that does extra
|
|
* manipulations with gv_keysize, gv_currkey and gv_altkey.
|
|
*/
|
|
reg = tr->reg;
|
|
if (!reg->open)
|
|
{
|
|
gv_init_reg(reg);
|
|
assert(reg->open);
|
|
}
|
|
}
|
|
DBG_CHECK_TP_REG_LIST_SORTING(tp_reg_list);
|
|
}
|
|
DEBUG_ONLY(TREF(ok_to_call_wcs_recover) = TRUE);
|
|
UNIX_ONLY(if (!jgbl.onlnrlbk))
|
|
tp_tend_status = tp_crit_all_regions(); /* grab crits on all regions */
|
|
/* else online rollback in which case we already hold crit on all regions */
|
|
/* It is possible we came into tp_restart to handle a different restart code but as part of the final
|
|
* retry detected an Online Rollback. If so, we shouldn't go into the final retry with this out-of-sync
|
|
* state as tp_tend will detect this out-of-sync during validation causing restart in the final
|
|
* retry resulting in TPFAIL. So, check for a concurrent Online Rollback and handle it if needed. It is
|
|
* enough to check any region as Online Rollback will increment the cycle field ON all the regions
|
|
* unconditionally
|
|
*/
|
|
# ifdef UNIX
|
|
if (cs_addrs_list && (cs_addrs_list->onln_rlbk_cycle != cs_addrs_list->nl->onln_rlbk_cycle))
|
|
{ /* We came in to handle a different restart code in the penultimate retry and grab_crit before going
|
|
* to final retry. As part of grabbing crit, we detected an online rollback. Although we could treat
|
|
* this as just an online rollback restart and handle it by syncing cycles, but by doing so, we will
|
|
* loose the information that an online rollback happened when we go back to gvcst_{put,kill}. This
|
|
* is usually fine except when we are in implicit TP (due to triggers). In case of implicit TP,
|
|
* gvcst_{put,kill} has specific code to handle online rollback differently than other restart codes
|
|
* Because of this reason, we don't want to sync cycles but instead continue with the final retry.
|
|
* t_end/tp_tend/tp_hist will notice the cycle mismatch and will restart (once more) in final retry
|
|
* with the appropriate cdb_sc code which gvcst_put/gvcst_kill will intercept and act accordingly.
|
|
* Even if we are not syncing cycles, we need to reset the clues and root block numbers so that we
|
|
* proceeds smoothly in the final retry.
|
|
*/
|
|
RESET_ALL_GVT_CLUES;
|
|
}
|
|
# endif
|
|
DEBUG_ONLY(TREF(ok_to_call_wcs_recover) = FALSE);
|
|
assert(FALSE != tp_tend_status);
|
|
/* pick up all MM extension information */
|
|
for (tr = tp_reg_list; NULL != tr; tr = tr->fPtr)
|
|
{
|
|
reg = tr->reg;
|
|
if (dba_mm == reg->dyn.addr->acc_meth)
|
|
{
|
|
TP_CHANGE_REG_IF_NEEDED(reg);
|
|
MM_DBFILEXT_REMAP_IF_NEEDED(cs_addrs, gv_cur_region);
|
|
}
|
|
}
|
|
}
|
|
# ifdef GTM_TRIGGER
|
|
}
|
|
DBGTRIGR((stderr, "tp_restart: past initial normal state processing\n"));
|
|
# endif
|
|
/* The below code to determine the roll-back point depends on tp_frame sized blocks being pushed on the TP
|
|
* stack. If ever other sized blocks are pushed on, a different method will need to be found.
|
|
*/
|
|
assert(0 == ((tpstackbase - (unsigned char *)tp_pointer) % SIZEOF(tp_frame))); /* Simple check for above condition */
|
|
tf = (tp_frame *)(tpstackbase - (newlevel * SIZEOF(tp_frame)));
|
|
assert(NULL != tf);
|
|
assert(tpstacktop < (unsigned char *)tf);
|
|
UNIX_ONLY(issue_REPLONLNRLBK = FALSE);
|
|
# ifdef GTM_TRIGGER
|
|
if (TPRESTART_STATE_NORMAL == tprestart_state)
|
|
{ /* Only if normal tp_restart call - else we've already done this for this tp_restart */
|
|
# endif
|
|
/* Before we get too far unwound here, if this is a nonrestartable transaction,
|
|
* let's record where we are for the message later on.
|
|
*/
|
|
if (FALSE == tf->restartable && IS_MCODE_RUNNING)
|
|
getzposition(TADR(tp_restart_entryref));
|
|
/* Do a rollback type cleanup (invalidate gv_target clues of read as well as updated blocks) */
|
|
tp_clean_up(TRUE);
|
|
/* Note: At this point, we are ready to begin the next retry. While we can sync the trigger cycles now to avoid
|
|
* further restarts, we don't need to because tp_set_sgm (done for each region that is updated in a TP transaction)
|
|
* does the syncing anyways.
|
|
*/
|
|
# ifdef UNIX
|
|
assert(cdb_sc_normal != status);
|
|
if ((cdb_sc_onln_rlbk1 == status) || (cdb_sc_onln_rlbk2 == status))
|
|
{ /* restarted due to online rollback */
|
|
RESET_ALL_GVT_CLUES;
|
|
assert(!TREF(only_reset_clues_if_onln_rlbk));
|
|
if (IS_MCODE_RUNNING)
|
|
{
|
|
assert(!is_updproc);
|
|
(TREF(dollar_zonlnrlbk))++;
|
|
}
|
|
issue_REPLONLNRLBK = is_updproc;
|
|
}
|
|
# endif
|
|
# ifdef GTM_TRIGGER
|
|
}
|
|
if (TPRESTART_STATE_TPUNW >= tprestart_state)
|
|
{ /* Either this is a normal tp_restart call or we ran into a trigger base frame while "tp_unwind"
|
|
* was running which required M and C stack unwinding before we could proceed so this call is
|
|
* being restarted.
|
|
*/
|
|
# endif
|
|
/* Note that this form of "tp_unwind" will not only unwind the TP stack but also most if not all of
|
|
* the M stackframe and mv_stent chain as well.
|
|
*/
|
|
GTMTRIG_ONLY(DBGTRIGR((stderr, "tp_restart: Beginning state 0/1 processing (state %d)\n", tprestart_state)));
|
|
tp_unwind(newlevel, RESTART_INVOCATION, &tprestart_rc);
|
|
assert(tf == tp_pointer); /* Needs to be true for now. Revisit when can restart to other than newlevel == 1 */
|
|
gd_header = tp_pointer->gd_header;
|
|
gv_target = tp_pointer->orig_gv_target;
|
|
gv_cur_region = tp_pointer->gd_reg;
|
|
TP_CHANGE_REG(gv_cur_region);
|
|
DBG_CHECK_GVTARGET_CSADDRS_IN_SYNC;
|
|
dollar_tlevel = newlevel;
|
|
top = gv_currkey->top;
|
|
/* ensure proper alignment before dereferencing tp_pointer->orig_key->end */
|
|
assert(0 == (((unsigned long)tp_pointer->orig_key) % SIZEOF(tp_pointer->orig_key->end)));
|
|
memcpy(gv_currkey, tp_pointer->orig_key, SIZEOF(*tp_pointer->orig_key) + tp_pointer->orig_key->end);
|
|
gv_currkey->top = top;
|
|
DBG_CHECK_GVTARGET_GVCURRKEY_IN_SYNC;
|
|
# ifdef GTM_TRIGGER
|
|
/* Maintenance of SFF_TRIGR_CALLD stack frame flag:
|
|
* - Set by gtm_trigger when trigger base frame is created. Purpose to prevent MUM_TSTART from restarting
|
|
* a frame making a call-in to a trigger (flag is checked in MUM_TSTART macro) because the mpc in the
|
|
* stack frame is not the return point to the frame, which is only available in the C stack.
|
|
* - Both TP restart and error handling unwinds can use MUM_TSTART to restart frame.
|
|
* - TP restart changes the mpc to the proper address (where TSTART was done) before invoking MUM_TSTART. We allow
|
|
* this by shutting the SFF_TRIGR_CALLD flag off when mpc is changed.
|
|
* - For TSTARTs done implcitly by triggers, MUM_TSTART would break things so we do not turn off the flag
|
|
* for that type.
|
|
*/
|
|
if (!tp_pointer->implicit_tstart)
|
|
{ /* SFF_TRIGR_CALLD validation:
|
|
* - This is not a trigger-initiated implicit TSTART.
|
|
* - If the flag is is not on, no further checks. Turning off flag is unconditional for best performance.
|
|
* - If flag is on, verify the address in the stack frame is in fact being modified so it points to
|
|
* a TSTART instead of the (currently) trigger call point.
|
|
*/
|
|
assert(!(tp_pointer->fp->flags & SFF_TRIGR_CALLD) || (tp_pointer->fp->mpc != tp_pointer->restart_pc));
|
|
tp_pointer->fp->flags &= SFF_TRIGR_CALLD_OFF;
|
|
DBGTRIGR((stderr, "tp_restart: Removing SFF_TRIGR_CALLD in frame 0x"lvaddr"\n", tp_pointer->fp));
|
|
}
|
|
# endif
|
|
tp_pointer->fp->mpc = tp_pointer->restart_pc;
|
|
tp_pointer->fp->ctxt = tp_pointer->restart_ctxt;
|
|
# ifdef GTM_TRIGGER
|
|
} else
|
|
assert(TPRESTART_STATE_MSTKUNW == tprestart_state);
|
|
/* From here on, all states run */
|
|
# endif
|
|
/* Make sure everything else added to the stack since the transaction started is unwound. Note this loop only
|
|
* has work to do if there were NO local vars to restore. Otherwise tp_unwind would have already unwound the
|
|
* stack for us.
|
|
*/
|
|
GTMTRIG_ONLY(DBGTRIGR((stderr, "tp_restart: beginning frame unwinds (state %d)\n", tprestart_state)));
|
|
while (frame_pointer < tf->fp)
|
|
{
|
|
# ifdef GTM_TRIGGER
|
|
if (SFT_TRIGR & frame_pointer->type)
|
|
{ /* We have encountered a trigger base frame. We cannot unroll it because there are C frames
|
|
* associated with it so we must interrupt this tp_restart and return to gtm_trigger() so
|
|
* it can unroll the base frame and rethrow the error to properly unroll the C stack.
|
|
*/
|
|
tprestart_rc = ERR_TPRETRY;
|
|
tprestart_state = TPRESTART_STATE_MSTKUNW;
|
|
DBGTRIGR((stderr, "tp_restart: Encountered trigger base frame in M-stack unwind - rethrowing\n"));
|
|
INVOKE_RESTART;
|
|
}
|
|
# endif
|
|
op_unwind();
|
|
}
|
|
/* From here on, no further rethrows of tp_restart() - the final finishing touches */
|
|
assert((msp <= stackbase) && (msp > stacktop));
|
|
assert((mv_chain <= (mv_stent *)stackbase) && (mv_chain > (mv_stent *)stacktop));
|
|
assert(MVST_TPHOLD == tf->mvc->mv_st_type);
|
|
/* Our stack frames are unwound to the correct frame but there could be mv_stents pushed on after we last (re)started
|
|
* this transaction. We need to get rid of them to get back to the correct restart state.
|
|
*/
|
|
for (mvc = mv_chain; mvc < tf->mvc;)
|
|
{ /* Make sure we don't unwind the MVST_TPHOLD for our target level */
|
|
assert((MVST_TPHOLD != mvc->mv_st_type) || ((newlevel - 1) != mvc->mv_st_cont.mvs_tp_holder.tphold_tlevel));
|
|
DBGEHND((stderr, "tp_restart: unwinding mv_stent addr 0x"lvaddr" type %d\n", mvc, mvc->mv_st_type));
|
|
unw_mv_ent(mvc);
|
|
mvc = (mv_stent *)(mvc->mv_st_next + (char *)mvc);
|
|
}
|
|
assert((void *)mvc < (void *)frame_pointer);
|
|
assert(mvc == tf->mvc);
|
|
assert(mvc->mv_st_cont.mvs_tp_holder.tphold_tlevel == (dollar_tlevel - 1));
|
|
DBGEHND((stderr, "tp_restart: Resetting msp from 0x"lvaddr", to 0x"lvaddr" (diff=%d)\n",
|
|
msp, mvc, INTCAST((unsigned char *)mvc - msp)));
|
|
mv_chain = mvc;
|
|
msp = (unsigned char *)mvc;
|
|
# ifdef GTM_TRIGGER
|
|
/* Revert $ZTWormhole to its previous value */
|
|
DBGTRIGR((stderr, "tp_restart: Restoring $ZTWORMHOLE and NULLifying $ZTSLATE (state %d)\n", tprestart_state));
|
|
memcpy(&dollar_ztwormhole, &mvc->mv_st_cont.mvs_tp_holder.ztwormhole_save, SIZEOF(mval));
|
|
if (1 == newlevel)
|
|
memcpy(&dollar_ztslate, &literal_null, SIZEOF(mval)); /* Zap $ZTSLate at (re)start of lvl 1 transaction */
|
|
# endif
|
|
assert(curr_symval == tf->sym);
|
|
if (frame_pointer->flags & SFF_UNW_SYMVAL)
|
|
{ /* A symval was popped in THIS stackframe by one of our last mv_stent unwinds which means
|
|
* l_symtab is fairly borked.
|
|
*/
|
|
assert(frame_pointer->l_symtab); /* Would be NULL in replication processor */
|
|
if ((unsigned char *)frame_pointer->l_symtab < msp)
|
|
{ /* This condition is set up when a local routine is called which, since it is using the
|
|
* same code, uses the same l_symtab as the caller. But when an exclusive new is done in
|
|
* this frame, op_xnew creates a NEW symtab just for this frame. But when this code
|
|
* unwound back to the TSTART, we also unwound the l_symtab this frame was using. So here
|
|
* we verify this frame is a simple call frame from the previous and restore the use of its
|
|
* l_symtab if so. If not, GTMASSERT. Note the outer SFF_UWN_SYMVAL check keeps us from having
|
|
* non-existant l_symtab issues which is possible when we are MUPIP.
|
|
*/
|
|
if ((frame_pointer->rvector == frame_pointer->old_frame_pointer->rvector)
|
|
&& (frame_pointer->vartab_ptr == frame_pointer->old_frame_pointer->vartab_ptr))
|
|
{
|
|
frame_pointer->l_symtab = frame_pointer->old_frame_pointer->l_symtab;
|
|
frame_pointer->flags &= SFF_UNW_SYMVAL_OFF; /* No need to clear symtab now */
|
|
} else
|
|
GTMASSERT;
|
|
} else
|
|
{ /* Otherwise the l_symtab needs to be cleared so its references get re-resolved to *this* symtab */
|
|
memset(frame_pointer->l_symtab, 0, frame_pointer->vartab_len * SIZEOF(ht_ent_mname *));
|
|
frame_pointer->flags &= SFF_UNW_SYMVAL_OFF;
|
|
}
|
|
}
|
|
dollar_truth = tp_pointer->dlr_t;
|
|
dollar_zgbldir = tp_pointer->zgbldir;
|
|
GTMTRIG_ONLY(tprestart_state = TPRESTART_STATE_NORMAL);
|
|
if (FALSE == tf->restartable)
|
|
{ /* Transation is not restartable. Be sure to leave things in a state that the transaction could
|
|
* be continued if an error handler has a mind to do that.
|
|
*/
|
|
GTMTRIG_ONLY(DBGTRIGR((stderr, "tp_restart: Leaving tp_restart via TRESTNOT error - state reset to 0\n")));
|
|
if (IS_MCODE_RUNNING)
|
|
{
|
|
getzposition(&beganHere);
|
|
send_msg(VARLSTCNT(1) ERR_TRESTNOT); /* Separate msgs so we get both */
|
|
send_msg(VARLSTCNT(6) ERR_TRESTLOC, 4, beganHere.str.len, beganHere.str.addr,
|
|
(TREF(tp_restart_entryref)).str.len, (TREF(tp_restart_entryref)).str.addr);
|
|
rts_error(VARLSTCNT(8) ERR_TRESTNOT, 0, ERR_TRESTLOC, 4, beganHere.str.len, beganHere.str.addr,
|
|
(TREF(tp_restart_entryref)).str.len, (TREF(tp_restart_entryref)).str.addr);
|
|
} else
|
|
rts_error(VARLSTCNT(1) ERR_TRESTNOT);
|
|
return 0; /* for the compiler only -- never executed */
|
|
}
|
|
++dollar_trestart;
|
|
assert(dollar_trestart >= TREF(tp_restart_dont_counts));
|
|
assert(MAX_TRESTARTS > (dollar_trestart - TREF(tp_restart_dont_counts))); /* a magic number limit for restarts */
|
|
if (!dollar_trestart) /* in case of a wrap */
|
|
dollar_trestart--;
|
|
UNIX_ONLY(
|
|
/* Now that we are done with all the cleanup related to this restart, issue rts_error if we are update process.
|
|
* updproc_ch knows to handle this SIGNAL.
|
|
*/
|
|
if (issue_REPLONLNRLBK)
|
|
rts_error(VARLSTCNT(1) ERR_REPLONLNRLBK);
|
|
)
|
|
if (handle_errors_internally)
|
|
REVERT;
|
|
GTMTRIG_ONLY(DBGTRIGR((stderr, "tp_restart: completed\n")));
|
|
return 0;
|
|
}
|