/**************************************************************** * * * Copyright 2011, 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" #ifdef GTM_TRIGGER #include "error.h" #include "gdsroot.h" /* for gdsfhead.h */ #include "gdsbt.h" /* for gdsfhead.h */ #include "gdsfhead.h" #include "gvcst_protos.h" #include #include "gv_trigger.h" #include "gtm_trigger.h" #include "trigger.h" #include "trigger_fill_xecute_buffer.h" #include "trigger_gbl_fill_xecute_buffer.h" #include "trigger_read_name_entry.h" #include "trigger_source_read_andor_verify.h" #include "gvsub2str.h" /* for COPY_SUBS_TO_GVCURRKEY */ #include "format_targ_key.h" /* for COPY_SUBS_TO_GVCURRKEY */ #include "hashtab.h" /* for STR_HASH (in COMPUTE_HASH_MNAME) */ #include "targ_alloc.h" /* for SETUP_TRIGGER_GLOBAL & SWITCH_TO_DEFAULT_REGION */ #include "filestruct.h" /* for INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED (FILE_INFO) */ #include "mvalconv.h" #include "gdscc.h" /* needed for tp.h */ #include "gdskill.h" /* needed for tp.h */ #include "buddy_list.h" /* needed for tp.h */ #include "hashtab_int4.h" /* needed for tp.h */ #include "jnl.h" /* needed for tp.h */ #include "tp.h" /* for sgm_info */ #include "tp_frame.h" #include "tp_restart.h" #include "tp_set_sgm.h" #include "t_retry.h" #include "op.h" #include "op_tcommit.h" #include "memcoherency.h" #include "gtmimagename.h" #include "cdb_sc.h" #include "mv_stent.h" #include "gv_trigger_protos.h" GBLREF uint4 dollar_tlevel; GBLREF sgmnt_addrs *cs_addrs; GBLREF sgmnt_data_ptr_t cs_data; GBLREF gd_addr *gd_header; GBLREF gv_key *gv_currkey; GBLREF gd_region *gv_cur_region; GBLREF sgm_info *sgm_info_ptr; GBLREF gv_namehead *gv_target; GBLREF int tprestart_state; GBLREF tp_frame *tp_pointer; GBLREF int4 gtm_trigger_depth; GBLREF trans_num local_tn; GBLREF unsigned int t_tries; GBLREF unsigned char t_fail_hist[CDB_MAX_TRIES]; #ifdef DEBUG GBLREF boolean_t donot_INVOKE_MUMTSTART; #endif LITREF mval literal_batch; LITREF mval literal_hasht; STATICFNDCL CONDITION_HANDLER(trigger_source_raov_ch); STATICFNDCL int trigger_source_raov(mstr *trigname, trigger_action trigger_op); STATICFNDCL int trigger_source_raov_tpwrap_helper(mstr *trigname, trigger_action trigger_op); STATICFNDCL void trigger_source_raov_trigload(mstr *trigname, gv_trigger_t **ret_trigdsc); error_def(ERR_DBROLLEDBACK); error_def(ERR_TPRETRY); error_def(ERR_TRIGCOMPFAIL); error_def(ERR_TRIGNAMBAD); error_def(ERR_TRIGNAMENF); /* If we have an implicit transaction and are about to fire an error, commit the transaction first so we can * get rid of the transaction connotation before error handling gets involved. Note we use op_tcommit() here * instead of op_trollback so we can verify the conditions that generated the error. If some restartable * condition caused the error, this will restart and retry the transaction. Note that since skip_INVOKE_RESTART * is not set before this op_tcommit, it with throw a restart rather than returning a restartable code. */ #define CLEAR_IMPLICIT_TP_BEFORE_ERROR \ if (tp_pointer->implicit_trigger && (0 == gtm_trigger_depth)) \ { /* We have an implicit TP fence */ \ enum cdb_sc status; \ /* Eliminate transaction by commiting it (nothing was done) */ \ status = op_tcommit(); \ assert(cdb_sc_normal == status); \ } /* Similar condition handler to gvtr_tpwrap_ch except we don't insist on first_sgm_info being set */ CONDITION_HANDLER(trigger_source_raov_ch) { int rc; START_CH; if ((int)ERR_TPRETRY == SIGNAL) { /* This only happens at the outer-most TP layer so state should be normal now */ assert(TPRESTART_STATE_NORMAL == tprestart_state); tprestart_state = TPRESTART_STATE_NORMAL; rc = tp_restart(1, !TP_RESTART_HANDLES_ERRORS); assert(0 == rc); assert(TPRESTART_STATE_NORMAL == tprestart_state); /* No rethrows possible */ DBGTRIGR((stderr, "trigger_source_ch: Unwinding due to TP REstart\n")); UNWIND(NULL, NULL); } NEXTCH; } /* Routine to check on the named trigger. This routine is called from (at least) two places: the above get_src_line() * routine where it is called to retrieve trigger source (left in source definition buffer of the trigger) and the other * time it is called from op_setzbrk() to load and/or compile the trigger. In both cases, the trigger may be loaded or * its source loaded already so we perform a validation on it that it is the current trigger. If the trigger contents are * stale, Our actions depend on what mode we are in. If already in TP, we cause the trigger to be unloaded and signal a * restart. If not in TP, we just unload the trigger and work our full mojo on it to reload the current version. * * The following sitations can exist: * * 1. No trigger by the given name is loaded. For this situation, we need to locate and load the trigger and its source. * 2. Trigger is loaded but no source is in the trigger source buffer. For this situation, verify the trigger load is * current. If not, restart things. If is current, load the source. * 3. Trigger and source both loaded. Verify the trigger is current. if not restart things. * * In addition, we can be entered either with a TP FENCE already enabled or without one. How we deal with restarts varries * depending on which is true: * * - If in a TP fence already, if we hit a condition where we need to restart, we throw a trigger based restart condition * but because we aren't necessarily driving any triggers here, there is nothing in the restart process that actually * forces the trigger to reload before we come back here. So we call gvtr_free() on the region in question to force * those triggers to reload completely, even if it is us that ends up doing it when we get back here. * - If NOT under a TP fence already, we provide an implcit wrapper that will catch our restarts and reinvoke the logic * that will reload the trigger from scratch. * * Note, this routine is for loading trigger source when we are not driving triggers. The trigger_fill_xecute_buffer() * should be used when fetching source for trigger execution because it is lighter weight with built-in trigger refetch * logic since we are using the globals the triggers live in. In this case, the trigger access is adhoc for the $TEXT() * ZPRINT and ZBREAK uses. */ int trigger_source_read_andor_verify(mstr *trigname, trigger_action trigger_op) { gv_key *save_gv_currkey; gd_region *save_gv_cur_region; gv_namehead *save_gv_target; sgm_info *save_sgm_info_ptr; int src_fetch_status; sgmnt_addrs *csa; /* Used in SWITCH_TO_DEFAULT_REGION macro */ char save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)]; uint4 cycle; DEBUG_ONLY(unsigned int lcl_t_tries;) enum cdb_sc failure; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; assert(NULL != trigname); assert((NULL != trigname->addr) && (0 != trigname->len)); /* Before we try to save anything, see if there is something to save and initialize stuff if not */ if (NULL == gd_header) { /* If we do initialize things, start off in the default region since we need it shortly anyway */ gvinit(); SWITCH_TO_DEFAULT_REGION; } SAVE_TRIGGER_REGION_INFO; DBGTRIGR((stderr, "trigger_source_raov: Entered with trigger action %d\n", trigger_op)); /* First determination is if a TP fence is already in operation or not */ if (0 == dollar_tlevel) { /* We need a TP fence - provide one */ assert(!donot_INVOKE_MUMTSTART); DEBUG_ONLY(donot_INVOKE_MUMTSTART = TRUE); /* 0 ==> save no locals but RESTART OK. Note we do NOT mark this as a TRIGGER_IMPLICIT_TSTART because we * both have our own condition handler to take care of thrown restarts and because this is not a trigger * execution thing - just a load and possibly a trigger compile. */ op_tstart(IMPLICIT_TSTART, TRUE, &literal_batch, 0); for (;;) { /* Now that we are TP wrapped, fetch the trigger source lines from the ^#t global */ DEBUG_ONLY(lcl_t_tries = t_tries); src_fetch_status = trigger_source_raov_tpwrap_helper(trigname, trigger_op); if (0 == src_fetch_status) { assert(0 == dollar_tlevel); /* op_tcommit should have made sure of this */ break; } /* A restart has been signalled inside trigger fetch code for this possibly implicit TP wrapped * transaction. Redo source fetch logic. */ assert(ERR_TPRETRY == src_fetch_status); assert(CDB_STAGNATE >= t_tries); assert(0 < t_tries); assert((CDB_STAGNATE == t_tries) || (lcl_t_tries == t_tries - 1)); failure = t_fail_hist[t_tries - 1]; assert(((cdb_sc_onln_rlbk1 != failure) && (cdb_sc_onln_rlbk2 != failure)) || !gv_target || !gv_target->root); assert(((cdb_sc_onln_rlbk1 != failure) && (cdb_sc_onln_rlbk2 != failure)) || !IS_MCODE_RUNNING || TREF(dollar_zonlnrlbk)); if (cdb_sc_onln_rlbk2 == failure) rts_error(VARLSTCNT(1) ERR_DBROLLEDBACK); /* else if (cdb_sc_onln_rlbk1 == status) we don't need to do anything other than proceeding with the next * retry. Even though online rollback restart resets root block to zero for all gv_targets, ^#t root is * always established in gvtr_db_read_hasht (called below). We don't care about the root block being reset * for other gv_target because when they are referenced later in the process, op_gvname will be done and * that will anyways establish the root block numbers once again. */ } } else /* no return if TP restart */ src_fetch_status = trigger_source_raov(trigname, trigger_op); assert(0 == src_fetch_status); RESTORE_TRIGGER_REGION_INFO; return 0; } /* Now TP wrap and fetch the trigger source lines from the ^#t global */ STATICFNDEF int trigger_source_raov_tpwrap_helper(mstr *trigname, trigger_action trigger_op) { enum cdb_sc cdb_status; int rc; DBGTRIGR((stderr, "trigger_source_tpwrap_helper: Entered\n")); ESTABLISH_RET(trigger_source_raov_ch, SIGNAL); assert(donot_INVOKE_MUMTSTART); rc = trigger_source_raov(trigname, trigger_op); assert(0 == rc); /* Finish it now verifying it completed successfully */ GVTR_OP_TCOMMIT(cdb_status); if (cdb_sc_normal != cdb_status) { DBGTRIGR((stderr, "trigger_source_tpwrap_helper: Commit failed - throwing TP restart\n")); t_retry(cdb_status); } REVERT; return 0; } /* Routine to do the dirty work of resolving a trigger name into a trigger and perform the missing parts of * loading the trigger, loading the source, verifying proper source/trigger is loaded and compiling if * desired. If we complete successfully, returns 0. Error returns caught by condition handlers can return other values. */ STATICFNDEF int trigger_source_raov(mstr *trigname, trigger_action trigger_op) { mname_entry gvent; sgmnt_addrs *csa; sgmnt_data_ptr_t csd; rhdtyp *rtn_vector; gv_namehead *gvt, *hasht_tree; gvt_trigger_t *gvt_trigger; mstr gbl, xecute_buff; int index; mval trig_index; gv_trigger_t *trigdsc; uint4 cycle_start; boolean_t triggers_reloaded, db_trigger_cycle_mismatch, ztrig_cycle_mismatch; /* First lets locate the trigger. Try simple way first - lookup in routine name table */ if (NULL == (rtn_vector = find_rtn_hdr(trigname))) /* Note assignment */ { /* Wasn't found - look for it the harder way in the #t of the default region */ trigger_source_raov_trigload(trigname, &trigdsc); } else { /* Have a routine header addr. From that we can get the gv_trigger_t descriptor and from that, the * gvt_trigger and other necessaries */ trigdsc = (gv_trigger_t *)rtn_vector->trigr_handle; gvt_trigger = trigdsc->gvt_trigger; /* We now know our base block now */ index = trigdsc - gvt_trigger->gv_trig_array + 1; /* We now know our trigger index value */ gvt = gv_target = gvt_trigger->gv_target; /* gv_target contains global name */ gbl.addr = gvt->gvname.var_name.addr; gbl.len = gvt->gvname.var_name.len; TP_CHANGE_REG_IF_NEEDED(gvt->gd_csa->region); csa = cs_addrs; csd = csa->hdr; assert(csd == cs_data); /* Verify trigger is current. Note we use CSA for this check since within this transaction we could have multiple * triggers from the same global in flight preventing us from reloading a trigger. By checking CSA, we at least get * a consistent trigger view and depend on CSA being checked as current in op_tcommit. */ triggers_reloaded = FALSE; SHM_READ_MEMORY_BARRIER; tp_set_sgm(); db_trigger_cycle_mismatch = (csa->db_trigger_cycle != gvt->db_trigger_cycle); ztrig_cycle_mismatch = (csa->db_dztrigger_cycle && (gvt->db_dztrigger_cycle != csa->db_dztrigger_cycle)); if (db_trigger_cycle_mismatch || ztrig_cycle_mismatch) { /* The process' view of the triggers is stale. We cannot proceed unless the triggers get reloaded. * If triggers have been driven for this global in this transaction, we have to throw a restart. To * reload and go if triggers have already been driven creates a potential consistency issues plus * the possibility that we could remove a trigger actively running which will cause major issues * when the trigger returns. * * To prevent these sort of issues, we compare the local_tn value when the last trigger was driven * in this global (recorded by gtm_trigger() in gvt->trig_local_tn) to the current local_tn value. * If the same, we have to restart. Else, we can reload the triggers and keep going. * * Triggers can be invoked only by GT.M and Update process. Out of these, we expect only * GT.M to see restarts due to concurrent trigger changes. Update process is the only * updater on the secondary so we dont expect it to see any concurrent trigger changes * Assert accordingly. Note similar asserts occur in t_end.c and tp_tend.c. */ DBGTRIGR((stderr, "trigger_source_raov: Trigger cycle difference detected - db_trigger_cycle - " "csa: %d, csd: %d, gvt: %d db_ztrigger_cycle: csa: %d, gvt: %d\n", csa->db_trigger_cycle, csd->db_trigger_cycle, gvt->db_trigger_cycle, csa->db_dztrigger_cycle, gvt->db_dztrigger_cycle)); assert(IS_GTM_IMAGE); if ((local_tn == gvt->trig_local_tn) && db_trigger_cycle_mismatch) { /* Already dispatched trigger for this gvn in this transaction - must restart. But do so ONLY * if the process' trigger view changed because of a concurrent trigger load/unload and NOT * because of $ZTRIGGER as part of this transaction as that could cause unintended restarts. */ assert(CDB_STAGNATE > t_tries); DBGTRIGR((stderr, "trigger_source_raov: throwing TP restart\n")); t_retry(cdb_sc_triggermod); } cycle_start = csa->db_trigger_cycle; gvtr_db_read_hasht(csa); gvt_trigger = gvt->gvt_trigger; if (NULL == gvt_trigger) { /* No triggers were loaded for this region (all gone now) */ CLEAR_IMPLICIT_TP_BEFORE_ERROR; rts_error(VARLSTCNT(4) ERR_TRIGNAMENF, 2, trigname->len, trigname->addr); } gvt->db_trigger_cycle = cycle_start; gvt->db_dztrigger_cycle = csa->db_dztrigger_cycle; DBGTRIGR((stderr, "trigger_source_raov: triggers reloaded - " "gvt->db_trigger_cycle updated to %d\n", gvt->db_trigger_cycle)); trigger_source_raov_trigload(trigname, &trigdsc); triggers_reloaded = TRUE; } else DBGTRIGR((stderr, "trigger_source_raov: trigger validated\n")); /* Only proceed with this next section at this point if triggers have not been reloaded. If they have * been reloaded, the rtn_vector address will have changed causing issues in this section. In that case, * we just need to fall out of this section to the common section which rebuilds things as necessary. */ if (!triggers_reloaded) { /* Triggers were not reloaded - see if we need to load the source or not */ if (TRIGGER_COMPILE == trigger_op) { /* This trigger has been verified so if it is already compiled, we are done */ if (NULL != trigdsc->rtn_desc.rt_adr) return 0; } /* Else we need the trigger source loaded */ if (0 == ((gv_trigger_t *)rtn_vector->trigr_handle)->xecute_str.str.len) { SETUP_TRIGGER_GLOBAL; INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED; assert(0 == trigdsc->xecute_str.str.len); /* Make sure not replacing/losing a buffer */ i2mval(&trig_index, index); xecute_buff.addr = trigger_gbl_fill_xecute_buffer(gbl.addr, gbl.len, &trig_index, NULL, (int4 *)&xecute_buff.len); trigdsc->xecute_str.str = xecute_buff; } } /* We have referenced this trigger's source. Mark it in gv_target so we know if we have to restart later * if trigger changes instead of just reloading it on-the-fly. */ gvt->trig_local_tn = local_tn; } /* If the trigger is not already compiled, it needs to be since the routine header is the method for obtaining the * trigger descriptor. If routine is already compiled, we don't need to compile it again. */ if ((TRIGGER_COMPILE == trigger_op) || (NULL == trigdsc->rtn_desc.rt_adr)) { if (0 != gtm_trigger_complink(trigdsc, TRUE)) { PRN_ERROR; /* Flush out any compiler messages for compile record */ rts_error(VARLSTCNT(4) ERR_TRIGCOMPFAIL, 2, trigdsc->rtn_desc.rt_name.len, trigdsc->rtn_desc.rt_name.addr); } assert(trigdsc->rtn_desc.rt_adr); assert(trigdsc->rtn_desc.rt_adr == CURRENT_RHEAD_ADR(trigdsc->rtn_desc.rt_adr)); /* If compile only, the source code is no longer needed so release it */ if ((TRIGGER_COMPILE == trigger_op) && (0 < trigdsc->xecute_str.str.len)) { free(trigdsc->xecute_str.str.addr); trigdsc->xecute_str.str.addr = NULL; trigdsc->xecute_str.str.len = 0; } } else { assert(TRIGGER_SRC_LOAD == trigger_op); assert(NULL != trigdsc->xecute_str.str.addr); assert(0 < trigdsc->xecute_str.str.len); } return 0; } /* Routine called when need triggers loaded for a given global */ STATICFNDEF void trigger_source_raov_trigload(mstr *trigname, gv_trigger_t **ret_trigdsc) { mname_entry gvent; mval val; char *ptr; int len; sgmnt_addrs *csa; sgmnt_data_ptr_t csd; gv_namehead *gvt, *hasht_tree; gvt_trigger_t *gvt_trigger; mstr gbl, xecute_buff; int index; mval trig_index; gv_trigger_t *trigdsc; uint4 cycle_start; /* Find region trigger name is in */ if (!trigger_read_name_entry(trigname, &val)) { /* Trigger name not found - nothing we can do */ CLEAR_IMPLICIT_TP_BEFORE_ERROR; rts_error(VARLSTCNT(4) ERR_TRIGNAMENF, 2, trigname->len, trigname->addr); } /* Extract region name and trigger index number from result */ ptr = val.str.addr; len = STRLEN(ptr); /* Do it this way since "val" has multiple fields null separated */ ptr += len; assert(('\0' == *ptr) && (val.str.len > len)); ptr++; A2I(ptr, val.str.addr + val.str.len, index); gbl.addr = val.str.addr; gbl.len = len; GV_BIND_NAME_ONLY(gd_header, &gbl); /* does tp_set_sgm() */ gvt = gv_target; assert(cs_addrs == gvt->gd_csa); csa = gvt->gd_csa; csd = csa->hdr; assert(cs_data == csd); /* We now know the global/region we need to load triggers for - go for it */ cycle_start = csd->db_trigger_cycle; gvtr_db_read_hasht(csa); gvt_trigger = gvt->gvt_trigger; if (NULL == gvt_trigger) { /* No trigger were loaded for this region (all gone now) */ CLEAR_IMPLICIT_TP_BEFORE_ERROR; rts_error(VARLSTCNT(4) ERR_TRIGNAMENF, 2, trigname->len, trigname->addr); } gvt->db_trigger_cycle = cycle_start; gvt->db_dztrigger_cycle = csa->db_dztrigger_cycle; gvt->trig_local_tn = local_tn; /* Mark this trigger as being referenced in this transaction */ DBGTRIGR((stderr, "trigger_source_raov_trigload: gvt->db_trigger_cycle updated to %d\n", gvt->db_trigger_cycle)); SETUP_TRIGGER_GLOBAL; INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED; trigdsc = &gvt_trigger->gv_trig_array[index - 1]; assert(0 == trigdsc->xecute_str.str.len); /* Make sure not replacing/losing a buffer */ i2mval(&trig_index, index); xecute_buff.addr = trigger_gbl_fill_xecute_buffer(gbl.addr, gbl.len, &trig_index, NULL, (int4 *)&xecute_buff.len); trigdsc->xecute_str.str = xecute_buff; *ret_trigdsc = trigdsc; return; } #endif /* GTM_TRIGGER */