int trigger_fill_xecute_buffer(gv_trigger_t *trigdsc) { int src_fetch_status; assert(!dollar_tlevel || (tstart_trigger_depth <= gtm_trigger_depth)); /* We have 3 cases to consider - all of which REQUIRE a TP fence to already be in effect. The reason for this is, if we * detect a restartable condition, we are going to cause this region's triggers to be unloaded which destroys the block * our parameter is pointing to so the restart logic MUST take place outside of this routine. * * 1. We have an active transaction due to an IMPLICIT TSTART done by trigger handling but the trigger level has not yet * been created. We don't need another TP wrapper in this case but we do need a condition handler to trap the thrown * retry to again prevent C stack unwind and return to the caller in the same shape that gtm_trigger would return. * 2. We have an active transaction due to an EXPLICIT M-code TSTART command. For this case, the trigger loads proceed * as normal with restarts handled in the regular automatic fashion. Note this case also covers the tp restarts done * by both the update process and mupip recover forward since those functions have their own way of intercepting and * dealing with restarts. To cover those cases, tp->implicit_tstart can be TRUE but tp_implicit_trigger MUST be * FALSE. * 3. We have an active transaction due to an IMPLICIT TSTART done by trigger handling and one or more triggers are * running. This becomes like case 2 since the restart will be handled by gtm_trigger and the proper thing will * be done. * * An extra note about case 3. Case 3 can be the identified case if in a nested trigger we are in trigger-no-mans-land * with a base frame for the nested trigger (having driven one of a set of parallel nested triggers) but no actual trigger * execution frame yet exists. This is really a case 1 situation with a nested trigger but it turns out that dealing with * like case 3 does the right thing because if/when mdb_condition_handler catches a thrown TPRETRY error, mdb_condition * handler will peal the nested trigger frame off before doing the restart which works for us and avoids issues of * multi-level implicit restarts we would otherwise have to handle. * * Note, this routine is for loading trigger source when the trigger is to be driven. The trigger_source_read_andor_verify() * routine should be used when fetching trigger source for reasons other than driving the triggers. This routine is lighter * weight but has a dependence on the restartability of the trigger-drive logic for getting the triggers reloaded as * necessary. */ assertpro(0 < dollar_tlevel); if (!tp_pointer->implicit_trigger /* Case 2 */ || (tp_pointer->implicit_tstart && tp_pointer->implicit_trigger && (tstart_trigger_depth != gtm_trigger_depth))) /* Case 3 */ { /* Test for Case 3/4 where we get to do very little: */ DBGTRIGR((stderr, "trigger_fill_xecute_buffer: Case 2/3\n")); assert((!tp_pointer->implicit_trigger) || (0 < gtm_trigger_depth)); trigger_fill_xecute_buffer_read_trigger_source(trigdsc); } else { /* Test for Case 1 where we only need a condition handler */ DBGTRIGR((stderr, "trigger_fill_xecute_buffer: Case 1\n")); assert(tp_pointer->implicit_tstart && tp_pointer->implicit_trigger); assert(tstart_trigger_depth == gtm_trigger_depth); ESTABLISH_RET(trigger_fill_xecute_buffer_ch, SIGNAL); trigger_fill_xecute_buffer_read_trigger_source(trigdsc); REVERT; } /* return our bounty to caller */ trigdsc->xecute_str.mvtype = MV_STR; return 0; /* Could return ERR_TPRETRY if return is via our condition handler */ }
/* Unwind a trigger level, pop all the associated mv_stents and dispense with the base frame. * Note that this unwind restores the condition handler stack pointer and correct gtm_trigger_depth value in * order to maintain these values properly in the event of a major unwind. This routine is THE routine to use to unwind * trigger base frames in all cases due to the cleanups it takes care of. */ void gtm_trigger_fini(boolean_t forced_unwind, boolean_t fromzgoto) { if (0 == (frame_pointer->type & SFT_TRIGR)) GTMASSERT; /* Would normally be an assert but potential frame stack damage so severe and resulting debug difficulty that we GTMASSERT instead. */ /* Unwind the trigger base frame */ op_unwind(); /* restore frame_pointer stored at msp (see base_frame.c) */ frame_pointer = *(stack_frame**)msp; msp += SIZEOF(stack_frame *); /* Remove frame save pointer from stack */ if (!forced_unwind) { /* Remove the "do not return to me" flag only on non-error unwinds. Note this flag may have already been * turned off by an earlier tp_restart if this is not an implicit_tstart situation. */ assert(!tp_pointer->implicit_tstart || (SFF_TRIGR_CALLD & frame_pointer->flags)); frame_pointer->flags &= SFF_TRIGR_CALLD_OFF; DBGTRIGR((stderr, "gtm_trigger_fini: turning off SFF_TRIGR_CALLD (2) in frame 0x"lvaddr"\n", frame_pointer)); } else { /* Error unwind, make sure certain cleanups are done */ # ifdef DEBUG assert(!dollar_tlevel || (tstart_trigger_depth <= gtm_trigger_depth)); if (tstart_trigger_depth == gtm_trigger_depth) /* Unwinding gvcst_put() so get rid of flag it potentially set */ donot_INVOKE_MUMTSTART = FALSE; # endif if (tp_pointer) { /* This TP transaction can never be allowed to commit if this is the first trigger * (see comment in tp_frame.h against "cannot_commit" field for details). */ if ((0 == gtm_trigger_depth) && !fromzgoto) { DBGTRIGR((stderr, "gtm_trigger: cannot_commit flag set to TRUE\n")) tp_pointer->cannot_commit = TRUE; } if ((tp_pointer->fp == frame_pointer) && tp_pointer->implicit_tstart) OP_TROLLBACK(-1); /* We just unrolled the implicitly started TSTART so unroll what it did */ } } DBGTRIGR((stderr, "gtm_trigger: POP: frame_pointer 0x%016lx ctxt value: 0x%016lx\n", frame_pointer, ctxt)); /* Re-allow interruptions now that our base frame is gone */ if (forced_unwind) { /* Since we are being force-unwound, we don't know the state of things except that it it should be either * the state we set it to or the ok-to-interrupt state. Assert that and if we are changing the state, * be sure to run the deferred handler. */ assert((INTRPT_IN_TRIGGER_NOMANS_LAND == intrpt_ok_state) || (INTRPT_OK_TO_INTERRUPT == intrpt_ok_state)); ENABLE_INTERRUPTS(intrpt_ok_state); } else { /* Normal unwind should be ok with this macro */ ENABLE_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND); } }
STATICFNDEF int gtm_trigger_invoke(void) { /* Invoke trigger M routine. Separate so error returns to gtm_trigger with proper retcode */ int rc; ESTABLISH_RET(gtm_trigger_ch, mumps_status); gtm_trigger_depth++; DBGTRIGR((stderr, "gtm_trigger: Dispatching trigger at depth %d\n", gtm_trigger_depth)); assert(0 < gtm_trigger_depth); assert(GTM_TRIGGER_DEPTH_MAX >= gtm_trigger_depth); /* Allow interrupts to occur while the trigger is running */ ENABLE_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND); rc = dm_start(); /* Now that we no longer have a trigger stack frame, we are back in trigger no-mans-land */ DEFER_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND); gtm_trigger_depth--; DBGTRIGR((stderr, "gtm_trigger: Trigger returns with rc %d\n", rc)); REVERT; assert(frame_pointer->type & SFT_TRIGR); assert(0 <= gtm_trigger_depth); return rc; }
void t_retry(enum cdb_sc failure) { tp_frame *tf; unsigned char *end, buff[MAX_ZWR_KEY_SZ]; short tl; sgmnt_addrs *csa; sgmnt_data_ptr_t csd; node_local_ptr_t cnl; # ifdef DEBUG unsigned int tries; # endif boolean_t skip_invoke_restart; boolean_t redo_root_search_done = FALSE; unsigned int local_t_tries; mstr gvname_mstr, reg_mstr; gd_region *restart_reg; mval t_restart_entryref; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; # ifdef GTM_TRIGGER skip_invoke_restart = skip_INVOKE_RESTART; /* note down global value in local variable */ GTMTRIG_ONLY(DBGTRIGR((stderr, "t_retry: entered\n"))); # else skip_invoke_restart = FALSE; /* no triggers so set local variable to default state */ # endif /* We expect t_retry to be invoked with an abnormal failure code. mupip reorg is the only exception and can pass * cdb_sc_normal failure code in case it finds a global variable existed at start of reorg, but not when it came * into mu_reorg and did a gvcst_search. It cannot confirm this unless it holds crit for which it has to wait * until the final retry which is why we accept this way of invoking t_retry. Assert accordingly. */ assert((cdb_sc_normal != failure) || mu_reorg_process); t_fail_hist[t_tries] = (unsigned char)failure; if (mu_reorg_process) CWS_RESET; DEBUG_ONLY(TREF(donot_commit) = FALSE;)
/* Restore given local variable from supplied TP restore entry into given symval. Note lvscan_anchor will only be non-NULL * for the final level we are restoring (but not unwinding). We don't need to restore counters for any vars except the * very last level. * * The return code is only used when unrolling the M stack runs into a trigger base frame which must be unrolled * by gtm_trigger. A non-zero return code signals to tp_unwind() that it needs to rethrow the tprestart error. */ int tp_unwind_restlv(lv_val *curr_lv, lv_val *save_lv, tp_var *restore_ent, lvscan_blk **lvscan_anchor, int *tprestart_rc) { ht_ent_mname *tabent; lv_val *inuse_lv; int elemindx; mv_stent *mvc; lvscan_blk *lvscan, *newlvscan; lvTree *lvt_child; boolean_t var_cloned; assert(curr_lv); assert(LV_IS_BASE_VAR(curr_lv)); assert(curr_lv->tp_var); DBGRFCT((stderr, "\ntp_unwind_restlv: Entered for varname: '%.*s' curr_lv: 0x"lvaddr" save_lv: 0x"lvaddr"\n", restore_ent->key.var_name.len, restore_ent->key.var_name.addr, curr_lv, save_lv)); DBGRFCT((stderr, "tp_unwind_restlv: tp_pointer/current: fp: 0x"lvaddr"/0x"lvaddr" mvc: 0x"lvaddr"/0x"lvaddr " symval: 0x"lvaddr"/0x"lvaddr"\n", tp_pointer->fp, frame_pointer, tp_pointer->mvc, mv_chain, tp_pointer->sym, curr_symval)); /* First get the stack in the position where we can actually process this entry. Need to make sure we are processing * the symbol table we need to be processing so unwind enough stuff to get there. */ if (curr_symval != tp_pointer->sym) { /* Unwind as many stackframes as are necessary up to the max */ while((curr_symval != tp_pointer->sym) && (frame_pointer < tp_pointer->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_TPUNW; DBGTRIGR((stderr, "tp_unwind: Encountered trigger base frame during M-stack unwind - " "rethrowing\n")); return -1; } # endif op_unwind(); } if (curr_symval != tp_pointer->sym) { /* Unwind as many mv_stents as are necessary up to the max */ mvc = mv_chain; while((curr_symval != tp_pointer->sym) && (mvc < tp_pointer->mvc)) { unw_mv_ent(mvc); mvc = (mv_stent *)(mvc->mv_st_next + (char *)mvc); } mv_chain = mvc; /* Final check */ assertpro(curr_symval == tp_pointer->sym); } } var_cloned = curr_lv->tp_var->var_cloned; if (var_cloned) { /* Var/tree has been copied (and modified) -- see about restoring it */ DBGRFCT((stderr, "\ntp_unwind_restlv: curr_lv was modified and cloned -- needs restoration\n")); if (NULL != restore_ent->key.var_name.addr) { /* Restore data into a named variable (hash table entry) * Step 1 -- find its hash table address to see what lv_val is there now. */ tabent = lookup_hashtab_mname(&((tp_pointer->sym)->h_symtab), &restore_ent->key); assert(tabent); /* Step 2 -- If lv_val is NOT the same as it was, then we must replace the lv_val * currently in use. Decrement its use count (which will delete it and the tree if * it is no longer used) and replace with desired previous lv_val whose use count * was incremented when it was saved. */ if (curr_lv != (inuse_lv = (lv_val *)tabent->value)) /* Note assignment */ { if (inuse_lv) DECR_BASE_REF_RQ(tabent, inuse_lv, FALSE); DBGRFCT((stderr, "tp_unwind: hte 0x"lvaddr" being reset from 0x"lvaddr" to 0x"lvaddr"\n", tabent, tabent->value, curr_lv)); tabent->value = (void *)curr_lv; INCR_TREFCNT(curr_lv); /* Back in the hash table, bump its reference */ } } /* Else, if restoring orphaned data, just prune the old var and copy in the saved tree (if one existed) */ /* Step 3 -- We have the correct lv_val in the hash table now but it has the wrong value. * Get rid of its current tree if any. */ if (lvt_child = LV_GET_CHILD(curr_lv)) /* Note assignment */ { DBGRFCT((stderr, "\ntp_unwind_restlv: Killing children of curr_lv 0x"lvaddr"\n", curr_lv)); assert((lvTreeNode *)curr_lv == LVT_PARENT(lvt_child)); LV_CHILD(curr_lv) = NULL; /* prevent recursion due to alias containers */ lv_killarray(lvt_child, FALSE); } /* Step 4: Copy in the needed fields from the saved flavor lv_val back to curr_lv. * Preserve the ref counts of the current var since the copy's ref counts have not been kept up to date. */ DBGRFCT((stderr, "\ntp_unwind_restlv: Restoring value of lv 0x"lvaddr" back into lv 0x"lvaddr"\n", save_lv, curr_lv)); /* The following is optimized to do the initialization of just the needed structure members. For that it assumes a * particular "lv_val" structure layout. The assumed layout is asserted so any changes to the layout will * automatically show an issue here and cause the below initialization to be accordingly reworked. */ assert(0 == OFFSETOF(lv_val, v)); assert(OFFSETOF(lv_val, v) + SIZEOF(curr_lv->v) == OFFSETOF(lv_val, ptrs)); assert(OFFSETOF(lv_val, ptrs) + SIZEOF(curr_lv->ptrs) == OFFSETOF(lv_val, stats)); assert(OFFSETOF(lv_val, stats) + SIZEOF(curr_lv->stats) == OFFSETOF(lv_val, has_aliascont)); assert(OFFSETOF(lv_val, has_aliascont) + SIZEOF(curr_lv->has_aliascont) == OFFSETOF(lv_val, lvmon_mark)); assert(OFFSETOF(lv_val, lvmon_mark) + SIZEOF(curr_lv->lvmon_mark) == OFFSETOF(lv_val, tp_var)); assert(OFFSETOF(lv_val, tp_var) + SIZEOF(curr_lv->tp_var) == SIZEOF(lv_val)); /* save_lv -> curr_lv Copy begin */ curr_lv->v = save_lv->v; curr_lv->ptrs = save_lv->ptrs; assert(0 < curr_lv->stats.trefcnt); /* No need to copy "stats" as curr_lv is more uptodate */ assert(0 < curr_lv->stats.crefcnt); assert(8 == (OFFSETOF(lv_val, tp_var) - OFFSETOF(lv_val, has_aliascont))); curr_lv->has_aliascont = save_lv->has_aliascont; DBGALS_ONLY(curr_lv->lvmon_mark = save_lv->has_aliascont); assert(save_lv->tp_var == curr_lv->tp_var); /* no need to copy this field */ /* save_lv -> curr_lv Copy done */ /* Some fixup may need to be done if the variable was cloned (and thus moved around) */ curr_lv->tp_var->var_cloned = FALSE; if (lvt_child = LV_GET_CHILD(curr_lv)) { /* Some pointer fix up needs to be done since the owner of the restored tree changed */ assert(LVT_PARENT(lvt_child) == ((lvTreeNode *)curr_lv->tp_var->save_value)); LV_CHILD(save_lv) = NULL; /* now that curr_lv->tp_var->var_cloned has been reset */ LVT_PARENT(lvt_child) = (lvTreeNode *)curr_lv; if (curr_lv->has_aliascont && (NULL != lvscan_anchor)) { /* Some ref counts need to be restored for arrays this tree points to -- but only if the * array contains pointers (alias containers). */ DBGRFCT((stderr, "\ntp_unwind_restlv: Putting lv 0x:"lvaddr" on the lvscan list\n", curr_lv)); /* This array needs to have container pointer target reference counts reestablished. Record * the lv so this can happen after all vars are restored. */ lvscan = *lvscan_anchor; elemindx = lvscan->elemcnt++; /* Note post increment so elemindx has minus-one value */ if (ARY_SCNCNTNR_MAX < elemindx) { /* Present block is full so allocate a new one and chain it on */ lvscan->elemcnt--; /* New element ended up not being in that block.. */ newlvscan = (lvscan_blk *)malloc(SIZEOF(lvscan_blk)); newlvscan->next = lvscan; newlvscan->elemcnt = 1; /* Going to use first one *now* */ elemindx = 0; *lvscan_anchor = newlvscan; lvscan = newlvscan; } assert((ARY_SCNCNTNR_MAX >= elemindx) && (0 <= elemindx)); lvscan->ary_scncntnr[elemindx] = curr_lv; } } } else { DBGRFCT((stderr, "\ntp_unwind_restlv: curr_lv was NOT modified or cloned\n")); assert(NULL == LV_CHILD(save_lv)); assert(!save_lv->tp_var->var_cloned); /* We know that the subscript array underneath curr_lv did not change since saving it into save_lv. But the * unsubscripted lv could have changed (have no way of checking if that is the case) so restore it unconditionally. */ curr_lv->v = save_lv->v; /* No need to copy "save_lv->ptrs" as "ptrs" contains 2 fields both of which are already correct in "curr_lv" */ assert(save_lv->ptrs.val_ent.parent.sym == curr_lv->ptrs.val_ent.parent.sym); assert(NULL == save_lv->ptrs.val_ent.children); /* No need to copy "save_lv->stats" as "curr_lv->stats" is more uptodate */ assert(save_lv->has_aliascont == curr_lv->has_aliascont); /* no need to copy this field */ assert(save_lv->lvmon_mark == curr_lv->lvmon_mark); /* no need to copy this field */ assert(save_lv->tp_var == curr_lv->tp_var); /* no need to copy this field */ } if (NULL == lvscan_anchor) /* Means this is completely unwinding a nested level so we need to reset the tstartcycle in this * lvval so it gets handled correctly when this lv is encountered again after the restart completes. */ curr_lv->stats.tstartcycle = 0; return 0; }
gd_region *r_top, *r_local; gd_addr *addr_ptr; unsigned char tp_bat[TP_BATCH_LEN]; mname_entry tpvent; ht_ent_mname *tabent, *curent, *topent; sgmnt_addrs *csa; int4 shift_size; boolean_t tphold_noshift = FALSE, implicit_tstart; GTMTRIG_ONLY(boolean_t implicit_trigger;) DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; implicit_tstart = 0 != (implicit_flag & IMPLICIT_TSTART); GTMTRIG_ONLY(implicit_trigger = 0 != (implicit_flag & IMPLICIT_TRIGGER_TSTART)); GTMTRIG_ONLY(assert(!implicit_trigger || (implicit_trigger && implicit_tstart))); GTMTRIG_ONLY(DBGTRIGR((stderr, "op_tstart: Entered - dollar_tlevel: %d, implicit_flag: %d\n", dollar_tlevel, implicit_flag))); if (implicit_tstart) /* An implicit op_tstart is being done. In this case, even if we are in direct mode, we want to do * regular TPHOLD processing (no setting of tphold in the parent frame and shifting of all mv_stents). * This is ok because the life of the TP transaction will be done before the implicit operation()s are done * done so it will not persist across M lines like it normally would in direct mode. */ tphold_noshift = TRUE; /* If we haven't done any TP until now, turn the flag on to tell gvcst_init to initialize it in any regions it opens from now on and initialize it in any regions that are already open. */ if (!tp_in_use) { tp_in_use = TRUE; for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
void flush_jmp (rhdtyp *rtn_base, unsigned char *context, unsigned char *transfer_addr) { mv_stent *mv_st_ent, *mv_st_prev; char *top; unsigned char *msp_save; int4 shift, size, mv_st_type; rhdtyp *old_rtnhdr; unwind_nocounts(); /* We are going to mutate the current frame from the program it was running to the program we want it to run. * If the current frame is marked for indr cache cleanup, do that cleanup now and unmark the frame. */ IF_INDR_FRAME_CLEANUP_CACHE_ENTRY_AND_UNMARK(frame_pointer); DBGEHND((stderr, "flush_jmp: Retargetting stack frame 0x"lvaddr" for transfer address 0x"lvaddr"\n", frame_pointer, transfer_addr)); /* Also unmark the SFF_ETRAP_ERR bit in case it is set. This way we ensure control gets transferred to * the mpc below instead of "error_return" (which is what getframe will do in case the bit is set). * It is ok to clear this bit because the global variable "error_frame" will still be set to point to * this frame so whenever we unwind out of this, we will rethrow the error at the parent frame. */ assert(!(frame_pointer->flags & SFF_ETRAP_ERR) || (NULL == error_frame) || (error_frame == frame_pointer)); assert(!(SFT_TRIGR & frame_pointer->type)); frame_pointer->flags &= SFF_ETRAP_ERR_OFF; /* clear SFF_ETRAP_ERR bit */ frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF; /* clear SFF_IMPLTSTART_CALLD bit since this frame is being rewritten */ GTMTRIG_ONLY(DBGTRIGR((stderr, "flush_jmp: Turrning off SFF_IMPLTSTART_CALLD_OFF in frame 0x"lvaddr"\n", frame_pointer))); old_rtnhdr = frame_pointer->rvector; frame_pointer->rvector = rtn_base; /* Now that fp->rvector has been overwritten to new routine, check if the older routine had a "rtn_relinked" flag set * and if so that cleanup can be performed now. */ USHBIN_ONLY(CLEANUP_COPIED_RECURSIVE_RTN(old_rtnhdr)); /* cleanup if needed */ frame_pointer->vartab_ptr = (char *)VARTAB_ADR(rtn_base); frame_pointer->vartab_len = frame_pointer->rvector->vartab_len; frame_pointer->mpc = transfer_addr; frame_pointer->ctxt = context; #ifdef HAS_LITERAL_SECT frame_pointer->literal_ptr = (int4 *)LITERAL_ADR(rtn_base); #endif frame_pointer->temp_mvals = frame_pointer->rvector->temp_mvals; size = rtn_base->temp_size; frame_pointer->temps_ptr = (unsigned char *)frame_pointer - size; size += rtn_base->vartab_len * SIZEOF(ht_ent_mname *); frame_pointer->l_symtab = (ht_ent_mname **)((char *)frame_pointer - size); assert(frame_pointer->type & SFT_COUNT); assert((unsigned char *)mv_chain > stacktop && (unsigned char *)mv_chain <= stackbase); while (((char *)mv_chain < (char *)frame_pointer) && !mvs_save[mv_chain->mv_st_type]) { assert(MVST_TRIGR != mv_chain->mv_st_type); /* Should never unwind a trigger frame here */ msp = (unsigned char *)mv_chain; op_oldvar(); } if ((char *)mv_chain > (char *)frame_pointer) { msp_save = msp; msp = (unsigned char *)frame_pointer->l_symtab; if (msp <= stackwarn) { if (msp <= stacktop) { msp = msp_save; rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKOFLOW); } else rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKCRIT); } memset(msp, 0, size); DBGEHND((stderr, "flush_jmp: Old msp: 0x"lvaddr" New msp: 0x"lvaddr"\n", msp_save, msp)); return; } /* We kept one or more mv_stents for this frame. We may need to shift the stack to get room to create an l_symtab * for this re-purposed frame. In the above loop, we stopped searching the mv_stent chain at the first mv_stent we * knew we had to keep. Since we are moving things around anyway, see if there are any mv_stents associated * with this frame which don't need to be kept and can reclaim. */ mv_st_ent = mv_chain; mv_st_prev = (mv_stent *)((char *)mv_st_ent + mv_st_ent->mv_st_next); top = (char *)mv_st_ent + mvs_size[mv_st_ent->mv_st_type]; while ((char *)mv_st_prev < (char *)frame_pointer) { mv_st_type = mv_st_prev->mv_st_type; assert(MVST_TRIGR != mv_st_type); /* Should never unwind a trigger frame here */ if (!mvs_save[mv_st_type]) { /* Don't need to keep this mv_stent. Remove it from the chain */ DBGEHND((stderr, "flush_jmp: Removing no-save mv_stent addr 0x"lvaddr" and type %d\n", mv_st_prev, mv_st_type)); unw_mv_ent(mv_st_prev); mv_st_ent->mv_st_next += mv_st_prev->mv_st_next; mv_st_prev = (mv_stent *)((char *)mv_st_prev + mv_st_prev->mv_st_next); continue; } /* We found a previous mv_stent we need to keep. If we had an interveening mv_stent we don't need to * keep, migrate the new keeper mv_stent adjacent to the previous keeper. */ if (mv_st_prev != (mv_stent *)top) { DBGEHND((stderr, "flush_jmp: Migrating keeper mv_stent from 0x"lvaddr" to 0x"lvaddr" type %d\n", mv_st_prev, top, mv_st_type)); if (MVST_TPHOLD == mv_st_type) { /* If we are moving an MVST_TPHOLD mv_stent, find it in the tpstack and fix its * address there too. Else we won't unwind to the correct place on a restart. */ fix_tphold_mvc(top, (char *)mv_st_prev, ((char *)mv_st_prev + mvs_size[MVST_TPHOLD])); } memmove(top, mv_st_prev, mvs_size[mv_st_type]); } DBGEHND((stderr, "flush_jmp: Updating offsets for mv_stent at addr 0x"lvaddr" type %d\n", mv_st_ent, mv_st_ent->mv_st_type)); mv_st_ent->mv_st_next = mvs_size[mv_st_ent->mv_st_type]; mv_st_ent = (mv_stent *)top; mv_st_ent->mv_st_next += (unsigned int)((char *)mv_st_prev - top); top += mvs_size[mv_st_ent->mv_st_type]; mv_st_prev = (mv_stent *)((char *)mv_st_ent + mv_st_ent->mv_st_next); } shift = (int4)((char *)frame_pointer - top - size); DBGEHND_ONLY(msp_save = msp); if (shift) { if ((unsigned char *)mv_chain + shift <= stackwarn) { if ((unsigned char *)mv_chain + shift <= stacktop) rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKOFLOW); else rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKCRIT); } DBGEHND((stderr, "flush_jmp: Shifting %d bytes of stack from 0x"lvaddr" to 0x"lvaddr" by %d bytes\n", INTCAST(top - (char *)mv_chain), mv_chain, (mv_chain + shift), shift)); /* Since we are moving one or more mv_stents, it is no more difficult to check the range against the * tp_frame chain than it is to loop through the mv_stents checking each one since the tp_frame stack * is usually no more than 1-3 deep. */ fix_tphold_mvc(((char *)mv_chain + shift), (char *)mv_chain, ((char *)mv_chain + (top - (char *)mv_chain))); memmove((char *)mv_chain + shift, mv_chain, top - (char *)mv_chain); mv_chain = (mv_stent *)((char *)mv_chain + shift); mv_st_ent = (mv_stent *)((char *)mv_st_ent + shift); mv_st_ent->mv_st_next -= shift; msp = (unsigned char *)mv_chain; } memset(frame_pointer->l_symtab, 0, size); DBGEHND((stderr, "flush_jmp: Old msp: 0x"lvaddr" New msp: 0x"lvaddr"\n", msp_save, msp)); return; }
void goframes(int4 frames) #endif { mval *ret_targ; GTMTRIG_ONLY(goframes_unwound_trigger = FALSE); for (ret_targ = NULL; frames--; ) { while (tp_pointer && tp_pointer->fp <= frame_pointer) { OP_TROLLBACK(-1); } if (0 == frames) { ret_targ = (mval *)get_ret_targ(NULL); /* If alias_retarg is non-NULL, *ret_targ would have been already initialized so no need to set it. * Setting it to literal_null in that case would cause reference counts to not be decremented later * in op_unwind/mdb_condition_handler so it is actually necessary to skip it in that case. */ if ((NULL != ret_targ) && (NULL == alias_retarg)) { *ret_targ = literal_null; ret_targ->mvtype |= MV_RETARG; } } skip_error_ret = TRUE; # ifdef GTM_TRIGGER if (!(SFT_TRIGR & frame_pointer->type)) { /* Normal frame unwind */ DBGTRIGR((stderr, "goframes: unwinding regular frame at %016lx\n", frame_pointer)); op_unwind(); DBGTRIGR((stderr, "goframes: after regular frame unwind: frame_pointer 0x%016lx ctxt value: 0x%016lx\n", frame_pointer, ctxt)); } else { /* Trigger base frame unwind (special case) */ DBGTRIGR((stderr, "goframes: unwinding trigger base frame at %016lx\n", frame_pointer)); gtm_trigger_fini(TRUE, fromzgoto); goframes_unwound_trigger = TRUE; } # else /* If triggers are not enabled, just a normal unwind */ DBGEHND((stderr, "goframes: unwinding regular frame at %016lx\n", frame_pointer)); op_unwind(); # endif assert(FALSE == skip_error_ret); /* op_unwind() should have read and reset this */ skip_error_ret = FALSE; /* be safe in PRO versions */ } # ifdef GTM_TRIGGER if (unwtrigrframe && (SFT_TRIGR & frame_pointer->type)) { /* If we landed on a trigger base frame after unwinding everything, we are in the same boat as if we had run into * one while we were unwinding. We cannot return this frame to (for example) zgoto which is going to morph it into * something else (unwtrigrframe only set when ZGOTO with entryref specified). So if the flag says we should never * land on a trigger frame, go ahead and unwind that one too. */ DBGTRIGR((stderr, "goframes: unwinding trailing trigger base frame at %016lx\n", frame_pointer)); gtm_trigger_fini(TRUE, fromzgoto); goframes_unwound_trigger = TRUE; } # endif return; }
void trans_code_cleanup(void) { stack_frame *fp, *fpprev; uint4 errmsg; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; assert(!(SFT_ZINTR & proc_act_type)); /* With no extra ztrap frame being pushed onto stack, we may miss error(s) * during trans_code if we don't check proc_act_type in addition to * frame_pointer->type below. */ if (SFT_ZTRAP == proc_act_type) { if (0 < dollar_ztrap.str.len) errmsg = ERR_ERRWZTRAP; else errmsg = ERR_ERRWETRAP; } else if (SFT_DEV_ACT == proc_act_type) errmsg = ERR_ERRWIOEXC; else errmsg = 0; proc_act_type = 0; if (TREF(compile_time)) { TREF(compile_time) = FALSE; if (stringpool.base != rts_stringpool.base) stringpool = rts_stringpool; } for (fp = frame_pointer; fp; fp = fpprev) { fpprev = fp->old_frame_pointer; # ifdef GTM_TRIGGER if (SFT_TRIGR & fpprev->type) fpprev = *(stack_frame **)(fpprev + 1); # endif if (fp->type & SFT_DM) break; if (fp->type & SFT_COUNT) { if ((ERR_ERRWZTRAP == errmsg) || (ERR_ERRWETRAP == errmsg)) { /* Whether ETRAP or ZTRAP we want to rethrow the error at one level down */ SET_ERROR_FRAME(fp); /* reset error_frame to point to the closest counted frame */ assert(fp->flags & SFF_ETRAP_ERR); /* Turn off any device exception related flags now that we are going to handle errors using * $ETRAP or $ZTRAP AT THE PARENT LEVEL only (no more device exceptions). */ dollar_ztrap.str.len = 0; ztrap_explicit_null = FALSE; fp->flags &= SFF_DEV_ACT_ERR_OFF; fp->flags &= SFF_ZTRAP_ERR_OFF; err_act = &dollar_etrap.str; break; } else if (ERR_ERRWIOEXC == errmsg) { /* Error while compiling device exception. Set SFF_ETRAP_ERR bit so control is transferred to * error_return() which in turn will rethrow the error AT THE SAME LEVEL in order to try and * use $ZTRAP or $ETRAP whichever is active. Also set the SFF_DEV_ACT_ERR bit to signify this * is a device exception that is rethrown instead of a ztrap/etrap error. Also assert that * the rethrow will not use IO exception again (thereby ensuring error processing will * eventually terminate instead of indefinitely recursing). */ fp->flags |= (SFF_DEV_ACT_ERR | SFF_ETRAP_ERR); assert(NULL == active_device); /* mdb_condition_handler should have reset it */ break; } else if ((ERR_ERRWZBRK == errmsg) || (ERR_ERRWEXC == errmsg)) { /* For typical exceptions in ZBREAK and ZSTEP, get back to direct mode */ dm_setup(); break; } else { /* The only known way to be here is if the command is a command given in direct mode as * mdb_condition_handler won't drive an error handler in that case which would be caught in * one of the above conditions. Not breaking out of the loop here means the frame will just * unwind and we'll break on the direct mode frame which will be redriven. If the parent frame * is not a direct mode frame, we'll assert in debug or break in pro and just continue. * to direct mode. */ assert(fp->flags && (SFF_INDCE)); if (!fp->old_frame_pointer || !(fp->old_frame_pointer->type & SFT_DM)) { assert(FALSE); break; } } } if (fp->type) { SET_ERR_CODE(fp, errmsg); } /* If this frame is indicated for cache cleanup, do that cleanup * now before we get rid of the pointers used by that cleanup. */ IF_INDR_FRAME_CLEANUP_CACHE_ENTRY_AND_UNMARK(fp); fp->mpc = CODE_ADDRESS(pseudo_ret); fp->ctxt = GTM_CONTEXT(pseudo_ret); fp->flags &= SFF_IMPLTSTART_CALLD_OFF; /* Frame enterable now with mpc reset */ GTMTRIG_ONLY(DBGTRIGR((stderr, "trans_code_cleanup: turning off SFF_IMPLTSTART_CALLD in frame 0x"lvaddr"\n", frame_pointer))); } TREF(transform) = TRUE; if (0 != errmsg) dec_err(VARLSTCNT(1) errmsg); }
/* Restore given local variable from supplied TP restore entry into given symval. Note lvscan_anchor will only be non-NULL * for the final level we are restoring (but not unwinding). We don't need to restore counters for any vars except the * very last level. * * The return code is only used when unrolling the M stack runs into a trigger base frame which must be unrolled * by gtm_trigger. A non-zero return code signals to tp_unwind() that it needs to rethrow the tprestart error. */ int tp_unwind_restlv(lv_val *curr_lv, lv_val *save_lv, tp_var *restore_ent, lvscan_blk **lvscan_anchor, int *tprestart_rc) { ht_ent_mname *tabent; lv_val *inuse_lv; int elemindx; mv_stent *mvc; lvscan_blk *lvscan, *newlvscan; lvTree *lvt_child; boolean_t var_cloned; assert(curr_lv); assert(LV_IS_BASE_VAR(curr_lv)); assert(curr_lv->tp_var); DBGRFCT((stderr, "\ntp_unwind_restlv: Entered for varname: '%.*s' curr_lv: 0x"lvaddr" save_lv: 0x"lvaddr"\n", restore_ent->key.var_name.len, restore_ent->key.var_name.addr, curr_lv, save_lv)); DBGRFCT((stderr, "tp_unwind_restlv: tp_pointer/current: fp: 0x"lvaddr"/0x"lvaddr" mvc: 0x"lvaddr"/0x"lvaddr " symval: 0x"lvaddr"/0x"lvaddr"\n", tp_pointer->fp, frame_pointer, tp_pointer->mvc, mv_chain, tp_pointer->sym, curr_symval)); /* First get the stack in the position where we can actually process this entry. Need to make sure we are processing * the symbol table we need to be processing so unwind enough stuff to get there. */ if (curr_symval != tp_pointer->sym) { /* Unwind as many stackframes as are necessary up to the max */ while(curr_symval != tp_pointer->sym && frame_pointer < tp_pointer->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_TPUNW; DBGTRIGR((stderr, "tp_unwind: Encountered trigger base frame during M-stack unwind - " "rethrowing\n")); return -1; } # endif op_unwind(); } if (curr_symval != tp_pointer->sym) { /* Unwind as many mv_stents as are necessary up to the max */ mvc = mv_chain; while(curr_symval != tp_pointer->sym && mvc < tp_pointer->mvc) { unw_mv_ent(mvc); mvc = (mv_stent *)(mvc->mv_st_next + (char *)mvc); } mv_chain = mvc; /* Final check */ if (curr_symval != tp_pointer->sym) GTMASSERT; } } var_cloned = curr_lv->tp_var->var_cloned; if (var_cloned) { /* Var/tree has been copied (and modified) -- see about restoring it */ DBGRFCT((stderr, "\ntp_unwind_restlv: curr_lv was modified and cloned -- needs restoration\n")); if (NULL != restore_ent->key.var_name.addr) { /* Restore data into a named variable (hash table entry) * Step 1 -- find its hash table address to see what lv_val is there now. */ tabent = lookup_hashtab_mname(&((tp_pointer->sym)->h_symtab), &restore_ent->key); assert(tabent); /* Step 2 -- If lv_val is NOT the same as it was, then we must replace the lv_val * currently in use. Decrement its use count (which will delete it and the tree if * it is no longer used) and replace with desired previous lv_val whose use count * was incremented when it was saved. */ if (curr_lv != (inuse_lv = (lv_val *)tabent->value)) { if (inuse_lv) DECR_BASE_REF_RQ(tabent, inuse_lv, FALSE); DBGRFCT((stderr, "tp_unwind: hte 0x"lvaddr" being reset from 0x"lvaddr" to 0x"lvaddr"\n", tabent, tabent->value, curr_lv)); tabent->value = (void *)curr_lv; INCR_TREFCNT(curr_lv); /* Back in the hash table, bump its reference */ } } /* Else, if restoring orphaned data, just prune the old var and copy in the saved tree (if one existed) */ /* Step 3 -- We have the correct lv_val in the hash table now but it has the wrong value. * Get rid of its current tree if any. */ if (lvt_child = LV_GET_CHILD(curr_lv)) /* Note assignment */ { DBGRFCT((stderr, "\ntp_unwind_restlv: Killing children of curr_lv 0x"lvaddr"\n", curr_lv)); assert((lvTreeNode *)curr_lv == LVT_PARENT(lvt_child)); LV_CHILD(curr_lv) = NULL; /* prevent recursion due to alias containers */ lv_killarray(lvt_child, FALSE); } /* Step 4: Copy in the needed fields from the saved flavor lv_val back to curr_lv. * Preserve the ref counts of the current var since the copy's ref counts have not been kept up to date. */ DBGRFCT((stderr, "\ntp_unwind_restlv: Restoring value of lv 0x"lvaddr" back into lv 0x"lvaddr"\n", save_lv, curr_lv)); /* The following is optimized to do the initialization of just the needed structure members. For that it assumes a * particular "lv_val" structure layout. The assumed layout is asserted so any changes to the layout will * automatically show an issue here and cause the below initialization to be accordingly reworked. */ assert(0 == OFFSETOF(lv_val, v)); assert(OFFSETOF(lv_val, v) + SIZEOF(curr_lv->v) == OFFSETOF(lv_val, ptrs)); assert(OFFSETOF(lv_val, ptrs) + SIZEOF(curr_lv->ptrs) == OFFSETOF(lv_val, stats)); assert(OFFSETOF(lv_val, stats) + SIZEOF(curr_lv->stats) == OFFSETOF(lv_val, has_aliascont)); assert(OFFSETOF(lv_val, has_aliascont) + SIZEOF(curr_lv->has_aliascont) == OFFSETOF(lv_val, lvmon_mark)); assert(OFFSETOF(lv_val, lvmon_mark) + SIZEOF(curr_lv->lvmon_mark) == OFFSETOF(lv_val, tp_var)); assert(OFFSETOF(lv_val, tp_var) + SIZEOF(curr_lv->tp_var) == SIZEOF(lv_val)); /* save_lv -> curr_lv Copy begin */ curr_lv->v = save_lv->v; curr_lv->ptrs = save_lv->ptrs; assert(0 < curr_lv->stats.trefcnt); /* No need to copy "stats" as curr_lv is more uptodate */ assert(0 < curr_lv->stats.crefcnt); assert(8 == (OFFSETOF(lv_val, tp_var) - OFFSETOF(lv_val, has_aliascont))); /* lv_val->has_aliascont/lvmon_mark all initialized in one shot. * Note: We use "qw_num *" instead of "uint8 *" below because the former works on 32-bit platforms too. * On 64-bit platforms, this is 1 8-byte move instead of 2 4-byte moves (the latter means more instructions). * Even though lvmon_mark is maintained only if DEBUG_ALIAS is #defined, we do the copy here unconditionally * because on 64-bit platforms, a 4-byte copy (of just has_aliascont) is the same as an 8-byte copy * (of has_aliascont and lvmon_mark). On 32-bit platforms, it saves us one copy but we dont worry about it much. */ GTM64_ONLY(assert(IS_PTR_8BYTE_ALIGNED(&curr_lv->has_aliascont));) NON_GTM64_ONLY(assert(IS_PTR_4BYTE_ALIGNED(&curr_lv->has_aliascont));)
int gtm_trigger(gv_trigger_t *trigdsc, gtm_trigger_parms *trigprm) { mval *lvvalue; lnr_tabent *lbl_offset_p; uchar_ptr_t transfer_addr; lv_val *lvval; mname_entry *mne_p; uint4 *indx_p; ht_ent_mname *tabent; boolean_t added; int clrlen, rc, i, unwinds; mval **lvvalarray; mv_stent *mv_st_ent; symval *new_symval; uint4 dollar_tlevel_start; stack_frame *fp, *fpprev; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; assert(!skip_dbtriggers); /* should not come here if triggers are not supposed to be invoked */ assert(trigdsc); assert(trigprm); assert((NULL != trigdsc->rtn_desc.rt_adr) || ((MV_STR & trigdsc->xecute_str.mvtype) && (0 != trigdsc->xecute_str.str.len) && (NULL != trigdsc->xecute_str.str.addr))); assert(dollar_tlevel); /* Determine if trigger needs to be compiled */ if (NULL == trigdsc->rtn_desc.rt_adr) { /* No routine hdr addr exists. Need to do compile */ if (0 != gtm_trigger_complink(trigdsc, TRUE)) { PRN_ERROR; /* Leave record of what error caused the compilation failure if any */ 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)); /* Setup trigger environment stack frame(s) for execution */ if (!(frame_pointer->type & SFT_TRIGR)) { /* Create new trigger base frame first that back-stops stack unrolling and return to us */ if (GTM_TRIGGER_DEPTH_MAX < (gtm_trigger_depth + 1)) /* Verify we won't nest too deep */ rts_error(VARLSTCNT(3) ERR_MAXTRIGNEST, 1, GTM_TRIGGER_DEPTH_MAX); DBGTRIGR((stderr, "gtm_trigger: PUSH: frame_pointer 0x%016lx ctxt value: 0x%016lx\n", frame_pointer, ctxt)); /* Protect against interrupts while we have only a trigger base frame on the stack */ DEFER_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND); /* The current frame invoked a trigger. We cannot return to it for a TP restart or other reason unless * either the total operation (including trigger) succeeds and we unwind normally or unless the mpc is reset * (like what happens in various error or restart conditions) because right now it returns to where a database * command (KILL, SET or ZTRIGGER) was entered. Set flag in the frame to prevent MUM_TSTART unless the frame gets * reset. */ frame_pointer->flags |= SFF_TRIGR_CALLD; /* Do not return to this frame via MUM_TSTART */ DBGTRIGR((stderr, "gtm_trigger: Setting SFF_TRIGR_CALLD in frame 0x"lvaddr"\n", frame_pointer)); base_frame(trigdsc->rtn_desc.rt_adr); /* Finish base frame initialization - reset mpc/context to return to us without unwinding base frame */ frame_pointer->type |= SFT_TRIGR; # if defined(__hpux) && defined(__hppa) /* For HPUX-HPPA (PA-RISC), we use longjmp() to return to gtm_trigger() to avoid some some space register * corruption issues. Use call-ins already existing mechanism for doing this. Although we no longer support * HPUX-HPPA for triggers due to some unlocated space register error, this code (effectively always ifdef'd * out) left in in case it gets resurrected in the future (01/2010 SE). */ frame_pointer->mpc = CODE_ADDRESS(ci_ret_code); frame_pointer->ctxt = GTM_CONTEXT(ci_ret_code); # else frame_pointer->mpc = CODE_ADDRESS(gtm_levl_ret_code); frame_pointer->ctxt = GTM_CONTEXT(gtm_levl_ret_code); # endif /* This base stack frame is also where we save environmental info for all triggers invoked at this stack level. * Subsequent triggers fired at this level in this trigger invocation need only reinitialize a few things but * can avoid "the big save". */ if (NULL == trigr_symval_list) { /* No available symvals for use with this trigger, create one */ symbinit(); /* Initialize a symbol table the trigger will use */ curr_symval->trigr_symval = TRUE; /* Mark as trigger symval so will be saved not decommissioned */ } else { /* Trigger symval is available for reuse */ new_symval = trigr_symval_list; assert(new_symval->trigr_symval); trigr_symval_list = new_symval->last_tab; /* dequeue new curr_symval from list */ REINIT_SYMVAL_BLK(new_symval, curr_symval); curr_symval = new_symval; PUSH_MV_STENT(MVST_STAB); mv_chain->mv_st_cont.mvs_stab = new_symval; /* So unw_mv_ent() can requeue it for later use */ } /* Push our trigger environment save mv_stent onto the chain */ PUSH_MV_STENT(MVST_TRIGR); mv_st_ent = mv_chain; /* Initialize the mv_stent elements processed by stp_gcol which can be called by either op_gvsavtarg() or * by the extnam saving code below. This initialization keeps stp_gcol - should it be called - from attempting * to process unset fields filled with garbage in them as valid mstr address/length pairs. */ mv_st_ent->mv_st_cont.mvs_trigr.savtarg.str.len = 0; mv_st_ent->mv_st_cont.mvs_trigr.savextref.len = 0; mv_st_ent->mv_st_cont.mvs_trigr.dollar_etrap_save.str.len = 0; mv_st_ent->mv_st_cont.mvs_trigr.dollar_ztrap_save.str.len = 0; mv_st_ent->mv_st_cont.mvs_trigr.saved_dollar_truth = dollar_truth; op_gvsavtarg(&mv_st_ent->mv_st_cont.mvs_trigr.savtarg); if (extnam_str.len) { ENSURE_STP_FREE_SPACE(extnam_str.len); mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr = (char *)stringpool.free; memcpy(mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr, extnam_str.addr, extnam_str.len); stringpool.free += extnam_str.len; assert(stringpool.free <= stringpool.top); } mv_st_ent->mv_st_cont.mvs_trigr.savextref.len = extnam_str.len; mv_st_ent->mv_st_cont.mvs_trigr.ztname_save = dollar_ztname; mv_st_ent->mv_st_cont.mvs_trigr.ztdata_save = dollar_ztdata; mv_st_ent->mv_st_cont.mvs_trigr.ztoldval_save = dollar_ztoldval; mv_st_ent->mv_st_cont.mvs_trigr.ztriggerop_save = dollar_ztriggerop; mv_st_ent->mv_st_cont.mvs_trigr.ztupdate_save = dollar_ztupdate; mv_st_ent->mv_st_cont.mvs_trigr.ztvalue_save = dollar_ztvalue; mv_st_ent->mv_st_cont.mvs_trigr.ztvalue_changed_ptr = ztvalue_changed_ptr; # ifdef DEBUG /* In a debug process, these fields give clues of what trigger we are working on */ mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigdsc_last_save = trigdsc; mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigprm_last_save = trigprm; # endif assert(((0 == gtm_trigger_depth) && (ch_at_trigger_init == ctxt->ch)) || ((0 < gtm_trigger_depth) && (&mdb_condition_handler == ctxt->ch))); mv_st_ent->mv_st_cont.mvs_trigr.ctxt_save = ctxt; mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigger_depth_save = gtm_trigger_depth; if (0 == gtm_trigger_depth) { /* Only back up $*trap settings when initiating the first trigger level */ mv_st_ent->mv_st_cont.mvs_trigr.dollar_etrap_save = dollar_etrap; mv_st_ent->mv_st_cont.mvs_trigr.dollar_ztrap_save = dollar_ztrap; mv_st_ent->mv_st_cont.mvs_trigr.ztrap_explicit_null_save = ztrap_explicit_null; dollar_ztrap.str.len = 0; ztrap_explicit_null = FALSE; if (NULL != gtm_trigger_etrap.str.addr) /* An etrap was defined for the trigger environment - Else existing $etrap persists */ dollar_etrap = gtm_trigger_etrap; } mv_st_ent->mv_st_cont.mvs_trigr.mumps_status_save = mumps_status; mv_st_ent->mv_st_cont.mvs_trigr.run_time_save = run_time; /* See if a MERGE launched the trigger. If yes, save some state so ZWRITE, ZSHOW and/or MERGE can be * run in the trigger we dispatch. */ if ((0 != merge_args) || TREF(in_zwrite)) PUSH_MVST_MRGZWRSV; mumps_status = 0; run_time = TRUE; /* Previous value saved just above restored when frame pops */ } else { /* Trigger base frame exists so reinitialize the symbol table for new trigger invocation */ REINIT_SYMVAL_BLK(curr_symval, curr_symval->last_tab); /* Locate the MVST_TRIGR mv_stent containing the backed up values. Some of those values need * to be restored so the 2nd trigger has the same environment as the previous trigger at this level */ for (mv_st_ent = mv_chain; (NULL != mv_st_ent) && (MVST_TRIGR != mv_st_ent->mv_st_type); mv_st_ent = (mv_stent *)(mv_st_ent->mv_st_next + (char *)mv_st_ent)) ; assert(NULL != mv_st_ent); assert((char *)mv_st_ent < (char *)frame_pointer); /* Ensure mv_stent associated this trigger frame */ /* Reinit backed up values from the trigger environment backup */ dollar_truth = mv_st_ent->mv_st_cont.mvs_trigr.saved_dollar_truth; op_gvrectarg(&mv_st_ent->mv_st_cont.mvs_trigr.savtarg); extnam_str.len = mv_st_ent->mv_st_cont.mvs_trigr.savextref.len; if (extnam_str.len) memcpy(extnam_str.addr, mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr, extnam_str.len); mumps_status = 0; assert(run_time); /* Note we do not reset the handlers for parallel triggers - set one time only when enter first level * trigger. After that, whatever happens in trigger world, stays in trigger world. */ } assert(frame_pointer->type & SFT_TRIGR); # ifdef DEBUG gtm_trigdsc_last = trigdsc; gtm_trigprm_last = trigprm; # endif /* Set new value of trigger ISVs. Previous values already saved in trigger base frame */ dollar_ztname = &trigdsc->rtn_desc.rt_name; dollar_ztdata = (mval *)trigprm->ztdata_new; dollar_ztoldval = trigprm->ztoldval_new; dollar_ztriggerop = (mval *)trigprm->ztriggerop_new; dollar_ztupdate = trigprm->ztupdate_new; dollar_ztvalue = trigprm->ztvalue_new; ztvalue_changed_ptr = &trigprm->ztvalue_changed; /* Set values associated with trigger into symbol table */ lvvalarray = trigprm->lvvalarray; for (i = 0, mne_p = trigdsc->lvnamearray, indx_p = trigdsc->lvindexarray; i < trigdsc->numlvsubs; ++i, ++mne_p, ++indx_p) { /* Once thru for each subscript we are to set */ lvvalue = lvvalarray[*indx_p]; /* Locate mval that contains value */ assert(NULL != lvvalue); assert(MV_DEFINED(lvvalue)); /* No sense in defining the undefined */ lvval = lv_getslot(curr_symval); /* Allocate an lvval to put into symbol table */ LVVAL_INIT(lvval, curr_symval); lvval->v = *lvvalue; /* Copy mval into lvval */ added = add_hashtab_mname_symval(&curr_symval->h_symtab, mne_p, lvval, &tabent); assert(added); assert(NULL != tabent); } /* While the routine header is available in trigdsc, we also need the <null> label address associated with * the first (and only) line of code. */ lbl_offset_p = LNRTAB_ADR(trigdsc->rtn_desc.rt_adr); transfer_addr = (uchar_ptr_t)LINE_NUMBER_ADDR(trigdsc->rtn_desc.rt_adr, lbl_offset_p); /* Create new stack frame for invoked trigger in same fashion as gtm_init_env() creates its 2ndary frame */ # ifdef HAS_LITERAL_SECT new_stack_frame(trigdsc->rtn_desc.rt_adr, (unsigned char *)LINKAGE_ADR(trigdsc->rtn_desc.rt_adr), transfer_addr); # else /* Any platform that does not follow pv-based linkage model either * (1) uses the following calculation to determine the context pointer value, or * (2) doesn't need a context pointer */ new_stack_frame(trigdsc->rtn_desc.rt_adr, PTEXT_ADR(trigdsc->rtn_desc.rt_adr), transfer_addr); # endif dollar_tlevel_start = dollar_tlevel; assert(gv_target->gd_csa == cs_addrs); gv_target->trig_local_tn = local_tn; /* Record trigger being driven for this global */ /* Invoke trigger generated code */ rc = gtm_trigger_invoke(); if (1 == rc) { /* Normal return code (from dm_start). Check if TP has been unwound or not */ assert(dollar_tlevel <= dollar_tlevel_start); /* Bigger would be quite the surprise */ if (dollar_tlevel < dollar_tlevel_start) { /* Our TP level was unwound during the trigger so throw an error */ DBGTRIGR((stderr, "gtm_trigger: $TLEVEL less than at start - throwing TRIGTLVLCHNG\n")); gtm_trigger_fini(TRUE, FALSE); /* dump this trigger level */ rts_error(VARLSTCNT(4) ERR_TRIGTLVLCHNG, 2, trigdsc->rtn_desc.rt_name.len, trigdsc->rtn_desc.rt_name.addr); } rc = 0; /* Be polite and return 0 for the (hopefully common) success case */ } else if (ERR_TPRETRY == rc) { /* We are restarting the entire transaction. There are two possibilities here: * 1) This is a nested trigger level in which case we need to unwind further or * the outer trigger level was created by M code. If either is true, just * rethrow the TPRETRY error. * 2) This is the outer trigger and the call to op_tstart() was done by our caller. * In this case, we just return to our caller with a code signifying they need * to restart the implied transaction. */ assert(dollar_tlevel && (tstart_trigger_depth <= gtm_trigger_depth)); if ((tstart_trigger_depth < gtm_trigger_depth) || !tp_pointer->implicit_tstart || !tp_pointer->implicit_trigger) { /* Unwind a trigger level to restart level or to next trigger boundary */ gtm_trigger_fini(FALSE, FALSE); /* Get rid of this trigger level - we won't be returning */ DBGTRIGR((stderr, "gtm_trigger: dm_start returned rethrow code - rethrowing ERR_TPRETRY\n")); INVOKE_RESTART; } else { /* It is possible we are restarting a transaction that never got around to creating a base * frame yet the implicit TStart was done. So if there is no trigger base frame, do not * run gtm_trigger_fini() but instead do the one piece of cleanup it does that we still need. */ assert(donot_INVOKE_MUMTSTART); if (SFT_TRIGR & frame_pointer->type) { /* Normal case when TP restart unwinding back to implicit beginning */ gtm_trigger_fini(FALSE, FALSE); DBGTRIGR((stderr, "gtm_trigger: dm_start returned rethrow code - returning to gvcst_<caller>\n")); } else { /* Unusual case of trigger that died in no-mans-land before trigger base frame established. * Remove the "do not return to me" flag only on non-error unwinds */ assert(tp_pointer->implicit_tstart); assert(SFF_TRIGR_CALLD & frame_pointer->flags); frame_pointer->flags &= SFF_TRIGR_CALLD_OFF; DBGTRIGR((stderr, "gtm_trigger: turning off SFF_TRIGR_CALLD (1) in frame 0x"lvaddr"\n", frame_pointer)); DBGTRIGR((stderr, "gtm_trigger: unwinding no-base-frame trigger for TP restart\n")); } } /* Fall out and return ERR_TPRETRY to caller */ } else if (0 == rc) /* We should never get a return code of 0. This would be out-of-design and a signal that something * is quite broken. We cannot "rethrow" outside the trigger because it was not initially an error so * mdb_condition_handler would have no record of it (rethrown errors must have originally occurred in * or to be RE-thrown) and assert fail at best. */ GTMASSERT; else { /* We have an unexpected return code due to some error during execution of the trigger that tripped * gtm_trigger's safety handler (i.e. an error occurred in mdb_condition_handler() established by * dm_start(). Since we are going to unwind the trigger frame and rethrow the error, we also have * to unwind all the stack frames on top of the trigger frame. Figure out how many frames that is, * unwind them all plus the trigger base frame before rethrowing the error. */ for (unwinds = 0, fp = frame_pointer; (NULL != fp) && !(SFT_TRIGR & fp->type); fp = fp->old_frame_pointer) unwinds++; assert((NULL != fp) && (SFT_TRIGR & fp->type)); GOFRAMES(unwinds, TRUE, FALSE); assert((NULL != frame_pointer) && !(SFT_TRIGR & frame_pointer->type)); DBGTRIGR((stderr, "gtm_trigger: Unsupported return code (%d) - unwound %d frames and now rethrowing error\n", rc, unwinds)); rts_error(VARLSTCNT(1) ERR_REPEATERROR); } return rc; }
int gtm_trigger_complink(gv_trigger_t *trigdsc, boolean_t dolink) { char rtnname[GTM_PATH_MAX + 1], rtnname_template[GTM_PATH_MAX + 1]; char objname[GTM_PATH_MAX + 1]; char zcomp_parms[(GTM_PATH_MAX * 2) + SIZEOF(mident_fixed) + SIZEOF(OBJECT_PARM) + SIZEOF(NAMEOFRTN_PARM)]; mstr save_zsource; int rtnfd, rc, lenrtnname, lenobjname, len, alphnum_len, retry, save_errno; char *mident_suffix_p1, *mident_suffix_p2, *mident_suffix_top, *namesub1, *namesub2, *zcomp_parms_ptr; mval zlfile, zcompprm; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; DBGTRIGR_ONLY(memcpy(rtnname, trigdsc->rtn_desc.rt_name.addr, trigdsc->rtn_desc.rt_name.len)); DBGTRIGR_ONLY(rtnname[trigdsc->rtn_desc.rt_name.len] = 0); DBGTRIGR((stderr, "gtm_trigger_complink: (Re)compiling trigger %s\n", rtnname)); ESTABLISH_RET(gtm_trigger_complink_ch, ((0 == error_condition) ? TREF(dollar_zcstatus) : error_condition )); /* Verify there are 2 available chars for uniqueness */ assert((MAX_MIDENT_LEN - TRIGGER_NAME_RESERVED_SPACE) >= (trigdsc->rtn_desc.rt_name.len)); assert(NULL == trigdsc->rtn_desc.rt_adr); gtm_trigger_comp_prev_run_time = run_time; run_time = TRUE; /* Required by compiler */ /* Verify the routine name set by MUPIP TRIGGER and read by gvtr_db_read_hasht() is not in use */ if (NULL != find_rtn_hdr(&trigdsc->rtn_desc.rt_name)) { /* Ooops .. need name to be more unique.. */ /* Though variable definitions are conventionally done at the function entry, the reason alphanumeric_table * definition is done here is to minimize the time taken to initialize the below table in the most common case * (i.e. no trigger name collisions). */ char alphanumeric_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '\0'}; alphnum_len = STR_LIT_LEN(alphanumeric_table); namesub1 = trigdsc->rtn_desc.rt_name.addr + trigdsc->rtn_desc.rt_name.len++; /* If WBTEST_HELPOUT_TRIGNAMEUNIQ is defined, set alphnum_len to 1. This way, we make the maximum * possible combinations for the uniqe trigger names to be 3 which is significantly lesser than * the actual number of combinations (62x62 = 3844). For eg., if ^a is a global having triggers defined * in 4 global directories, then the possible unique trigger names are a#1# ; a#1#A ; a#1#AA. */ GTM_WHITE_BOX_TEST(WBTEST_HELPOUT_TRIGNAMEUNIQ, alphnum_len, 1); mident_suffix_top = (char *)alphanumeric_table + alphnum_len; /* Phase 1. See if any single character can add uniqueness */ for (mident_suffix_p1 = (char *)alphanumeric_table; mident_suffix_p1 < mident_suffix_top; mident_suffix_p1++) { *namesub1 = *mident_suffix_p1; if (NULL == find_rtn_hdr(&trigdsc->rtn_desc.rt_name)) break; } if (mident_suffix_p1 == mident_suffix_top) { /* Phase 2. Phase 1 could not find uniqueness .. Find it with 2 char variations */ namesub2 = trigdsc->rtn_desc.rt_name.addr + trigdsc->rtn_desc.rt_name.len++; for (mident_suffix_p1 = (char *)alphanumeric_table; mident_suffix_p1 < mident_suffix_top; mident_suffix_p1++) { /* First char loop */ for (mident_suffix_p2 = (char *)alphanumeric_table; mident_suffix_p2 < mident_suffix_top; mident_suffix_p2++) { /* 2nd char loop */ *namesub1 = *mident_suffix_p1; *namesub2 = *mident_suffix_p2; if (NULL == find_rtn_hdr(&trigdsc->rtn_desc.rt_name)) { mident_suffix_p1 = mident_suffix_top + 1; /* Break out of both loops */ break; } } } if (mident_suffix_p1 == mident_suffix_top) { /* Phase 3: Punt */ assert(WBTEST_HELPOUT_TRIGNAMEUNIQ == gtm_white_box_test_case_number); rts_error(VARLSTCNT(5) ERR_TRIGNAMEUNIQ, 3, trigdsc->rtn_desc.rt_name.len - 2, trigdsc->rtn_desc.rt_name.addr, alphnum_len * alphnum_len); } } } /* Write trigger execute string out to temporary file and compile it */ assert(MAX_XECUTE_LEN >= trigdsc->xecute_str.str.len); rc = SNPRINTF(rtnname_template, GTM_PATH_MAX, "%s/trgtmpXXXXXX", DEFAULT_GTM_TMP); assert(0 < rc); /* Note rc is return code aka length - we expect a non-zero length */ assert(GTM_PATH_MAX >= rc); /* The mkstemp() routine is known to bogus-fail for no apparent reason at all especially on AIX 6.1. In the event * this shortcoming plagues other platforms as well, we add a low-cost retry wrapper. */ retry = MAX_MKSTEMP_RETRIES; do { strcpy(rtnname, rtnname_template); rtnfd = mkstemp(rtnname); } while ((-1 == rtnfd) && (EEXIST == errno) && (0 < --retry)); if (-1 == rtnfd) { save_errno = errno; assert(FALSE); rts_error(VARLSTCNT(12) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("mkstemp()"), CALLFROM, ERR_TEXT, 2, RTS_ERROR_TEXT(rtnname), save_errno); } assert(0 < rtnfd); /* Verify file descriptor */ rc = 0; # ifdef GEN_TRIGCOMPFAIL_ERROR { /* Used ONLY to generate an error in a trigger compile by adding some junk in a previous line */ DOWRITERC(rtnfd, ERROR_CAUSING_JUNK, strlen(ERROR_CAUSING_JUNK), rc); /* BYPASSOK */ if (0 != rc) { UNLINK(rtnname); rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc); } } # endif DOWRITERC(rtnfd, trigdsc->xecute_str.str.addr, trigdsc->xecute_str.str.len, rc); if (0 != rc) { UNLINK(rtnname); rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc); } if (NULL == memchr(trigdsc->xecute_str.str.addr, '\n', trigdsc->xecute_str.str.len)) { DOWRITERC(rtnfd, NEWLINE, strlen(NEWLINE), rc); /* BYPASSOK */ if (0 != rc) { UNLINK(rtnname); rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc); } } CLOSEFILE(rtnfd, rc); if (0 != rc) { UNLINK(rtnname); rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("close()"), CALLFROM, rc); } assert(MAX_MIDENT_LEN > trigdsc->rtn_desc.rt_name.len); zcomp_parms_ptr = zcomp_parms; lenrtnname = STRLEN(rtnname); MEMCPY_LIT(zcomp_parms_ptr, NAMEOFRTN_PARM); zcomp_parms_ptr += STRLEN(NAMEOFRTN_PARM); memcpy(zcomp_parms_ptr, trigdsc->rtn_desc.rt_name.addr, trigdsc->rtn_desc.rt_name.len); zcomp_parms_ptr += trigdsc->rtn_desc.rt_name.len; MEMCPY_LIT(zcomp_parms_ptr, OBJECT_PARM); zcomp_parms_ptr += STRLEN(OBJECT_PARM); strcpy(objname, rtnname); /* Make copy of rtnname to become object name */ strcat(objname, OBJECT_FTYPE); /* Turn into object file reference */ lenobjname = lenrtnname + STRLEN(OBJECT_FTYPE); memcpy(zcomp_parms_ptr, objname, lenobjname); zcomp_parms_ptr += lenobjname; *zcomp_parms_ptr++ = ' '; memcpy(zcomp_parms_ptr, rtnname, lenrtnname); zcomp_parms_ptr += lenrtnname; *zcomp_parms_ptr = '\0'; /* Null tail */ len = INTCAST(zcomp_parms_ptr - zcomp_parms); assert((SIZEOF(zcomp_parms) - 1) > len); /* Verify no overflow */ zcompprm.mvtype = MV_STR; zcompprm.str.addr = zcomp_parms; zcompprm.str.len = len; /* Backup dollar_zsource so trigger doesn't show */ PUSH_MV_STENT(MVST_MSAV); mv_chain->mv_st_cont.mvs_msav.v = dollar_zsource; mv_chain->mv_st_cont.mvs_msav.addr = &dollar_zsource; TREF(trigger_compile) = TRUE; /* Set flag so compiler knows this is a special trigger compile */ op_zcompile(&zcompprm, FALSE); /* Compile but don't require a .m file extension */ TREF(trigger_compile) = FALSE; /* compile_source_file() establishes handler so always returns */ if (0 != TREF(dollar_zcstatus)) { /* Someone err'd.. */ run_time = gtm_trigger_comp_prev_run_time; REVERT; UNLINK(objname); /* Remove files before return error */ UNLINK(rtnname); return ERR_TRIGCOMPFAIL; } if (dolink) { /* Link is optional as MUPIP TRIGGER doesn't need link */ zlfile.mvtype = MV_STR; zlfile.str.addr = objname; zlfile.str.len = lenobjname; /* Specifying literal_null for a second arg (as opposed to NULL or 0) allows us to specify * linking the object file (no compilation or looking for source). The 2nd arg is parms for * recompilation and is non-null in an explicit zlink which we need to emulate. */ # ifdef GEN_TRIGLINKFAIL_ERROR UNLINK(objname); /* delete object before it can be used */ # endif op_zlink(&zlfile, (mval *)&literal_null); /* need cast due to "extern const" attributes */ /* No return here if link fails for some reason */ trigdsc->rtn_desc.rt_adr = find_rtn_hdr(&trigdsc->rtn_desc.rt_name); if (NULL == trigdsc->rtn_desc.rt_adr) GTMASSERT; /* Can't find routine we just put there? Catastrophic if happens */ /* Replace the randomly generated source name with the constant "GTM Trigger" */ trigdsc->rtn_desc.rt_adr->src_full_name.addr = GTM_TRIGGER_SOURCE_NAME; trigdsc->rtn_desc.rt_adr->src_full_name.len = STRLEN(GTM_TRIGGER_SOURCE_NAME); trigdsc->rtn_desc.rt_adr->trigr_handle = trigdsc; /* Back pointer to trig def */ } if (MVST_MSAV == mv_chain->mv_st_type && &dollar_zsource == mv_chain->mv_st_cont.mvs_msav.addr) { /* Top mv_stent is one we pushed on there - restore dollar_zsource and get rid of it */ dollar_zsource = mv_chain->mv_st_cont.mvs_msav.v; POP_MV_STENT(); } else assert(FALSE); /* This mv_stent should be the one we just pushed */ /* Remove temporary files created */ UNLINK(objname); /* Delete the object file first since rtnname is the unique key */ UNLINK(rtnname); /* Delete the source file */ run_time = gtm_trigger_comp_prev_run_time; REVERT; return 0; }
/* Workhorse of fetching source for given trigger. */ STATICFNDEF void trigger_fill_xecute_buffer_read_trigger_source(gv_trigger_t *trigdsc) { enum cdb_sc cdb_status; int4 index; mstr gbl, xecute_buff; mval trig_index; sgmnt_addrs *csa; sgmnt_data_ptr_t csd; gvt_trigger_t *gvt_trigger; gv_namehead *gvt; gv_namehead *save_gv_target; gd_region *save_gv_cur_region; sgm_info *save_sgm_info_ptr; gv_key save_currkey[DBKEYALLOC(MAX_KEY_SZ)]; assert(0 < dollar_tlevel); assert(NULL != trigdsc); SAVE_TRIGGER_REGION_INFO(save_currkey); 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 */ i2mval(&trig_index, index); DBGTRIGR((stderr, "trigger_fill_xecute_buffer_read_trigger_source: entry $tlevel:%d\tindex:%d of %d\n", dollar_tlevel, index, gvt_trigger->num_gv_triggers)); 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; /* Our situation is that while our desired gv_target has csa information, we don't know specifically * which global directory was in use so we can't run gv_bind_name() lest we find the given global * name in the wrong global directory thus running the wrong triggers. But we know this target is * properly formed since it had to be when it was recorded when the triggers were loaded. Because of * that, we can get the correct csa and gv_target and csa-region will point us to a region that will * work even if it isn't exactly the one we used to get to this trigger. */ TP_CHANGE_REG_IF_NEEDED(gvt->gd_csa->region); csa = cs_addrs; csd = csa->hdr; assert(csd == cs_data); tp_set_sgm(); /* See if we need to reload our triggers */ if ((csa->db_trigger_cycle != gvt->db_trigger_cycle) || (csa->db_dztrigger_cycle && (gvt->db_dztrigger_cycle != csa->db_dztrigger_cycle))) { /* The process' view of the triggers could be potentially stale. Restart to be safe. * 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. */ DBGTRIGR((stderr, "trigger_fill_xecute_buffer_read_trigger_source: stale trigger view\n")); assert(CDB_STAGNATE > t_tries); assert(IS_GTM_IMAGE); t_retry(cdb_sc_triggermod); } SET_GVTARGET_TO_HASHT_GBL(csa); INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED; assert(0 == trigdsc->xecute_str.str.len); /* Make sure not replacing/losing a buffer */ 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; /* Restore gv_target/gv_currkey which need to be kept in sync */ RESTORE_TRIGGER_REGION_INFO(save_currkey); return; }