/**************************************************************** * * * 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" #include "gtm_time.h" #include "gtmimagename.h" #ifdef UNIX # include # include "gtm_stat.h" # include # include #elif defined(VMS) # include # include # include #else # error UNSUPPORTED PLATFORM #endif #include "ast.h" /* needed for DCLAST_WCS_WTSTART macro in gdsfhead.h */ #include "gdsroot.h" #include "gdskill.h" #include "gtm_facility.h" #include "fileinfo.h" #include "gdsbt.h" #include "gdsblk.h" #include "gdsfhead.h" #include "gdsbgtr.h" #include "gdsbml.h" #include "filestruct.h" #include "gdscc.h" #include "interlock.h" #include "jnl.h" #include "testpt.h" #include "sleep_cnt.h" #include "mupipbckup.h" #include "wbox_test_init.h" #ifdef UNIX # include "eintr_wrappers.h" GBLREF sigset_t blockalrm; #endif #include "send_msg.h" #include "bit_set.h" #include "bit_clear.h" #include "relqop.h" #include "is_proc_alive.h" #include "mmseg.h" #include "format_targ_key.h" #include "gds_map_moved.h" #include "wcs_recover.h" #include "wcs_sleep.h" #include "wcs_mm_recover.h" #include "add_inter.h" #include "gtm_malloc.h" /* for verifyAllocatedStorage() prototype */ #include "cert_blk.h" #include "shmpool.h" #include "wcs_phase2_commit_wait.h" #include "buddy_list.h" /* needed for tp.h */ #include "hashtab_int4.h" /* needed for tp.h */ #include "tp.h" #include "memcoherency.h" #include "gtm_c_stack_trace.h" #ifdef GTM_TRUNCATE #include "recover_truncate.h" #endif GBLREF boolean_t certify_all_blocks; GBLREF sgmnt_addrs *cs_addrs; GBLREF sgmnt_data_ptr_t cs_data; GBLREF gd_region *gv_cur_region; GBLREF gv_key *gv_currkey; /* needed in VMS for error logging in MM */ GBLREF uint4 process_id; GBLREF testpt_struct testpoint; GBLREF inctn_opcode_t inctn_opcode; GBLREF boolean_t mupip_jnl_recover; GBLREF jnl_gbls_t jgbl; GBLREF enum gtmImageTypes image_type; GBLREF uint4 gtmDebugLevel; GBLREF unsigned int cr_array_index; GBLREF uint4 dollar_tlevel; GBLREF volatile boolean_t in_wcs_recover; /* TRUE if in "wcs_recover" */ GBLREF boolean_t is_src_server; #ifdef DEBUG GBLREF unsigned int t_tries; GBLREF int process_exiting; GBLREF boolean_t in_mu_rndwn_file; #endif error_def(ERR_BUFRDTIMEOUT); error_def(ERR_DBADDRALIGN); error_def(ERR_DBADDRANGE); error_def(ERR_DBCCERR); error_def(ERR_DBCNTRLERR); error_def(ERR_DBCRERR); error_def(ERR_DBDANGER); error_def(ERR_DBFILERR); error_def(ERR_ERRCALL); error_def(ERR_INVALIDRIP); error_def(ERR_GBLOFLOW); error_def(ERR_GVIS); error_def(ERR_STOPTIMEOUT); error_def(ERR_TEXT); void wcs_recover(gd_region *reg) { bt_rec_ptr_t bt; cache_rec_ptr_t cr, cr_alt, cr_alt_new, cr_lo, cr_top, hash_hdr; cache_que_head_ptr_t active_head, hq, wip_head, wq; gd_region *save_reg; que_ent_ptr_t back_link; /* should be crit & not need interlocked ops. */ sgmnt_addrs *csa; sgmnt_data_ptr_t csd; node_local_ptr_t cnl; int4 bml_full, dummy_errno, blk_size; uint4 jnl_status, epid, r_epid; int4 bt_buckets, bufindx; /* should be the same type as "csd->bt_buckets" */ inctn_opcode_t save_inctn_opcode; unsigned int bplmap, lcnt, total_blks, wait_in_rip; sm_uc_ptr_t buffptr; blk_hdr_ptr_t blk_ptr; INTPTR_T bp_lo, bp_top, old_block; boolean_t backup_block_saved, change_bmm; jnl_private_control *jpc; jnl_buffer_ptr_t jbp; sgm_info *si; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; /* If this is the source server, do not invoke cache recovery as that implies touching the database file header * (incrementing the curr_tn etc.) and touching the journal file (writing INCTN records) both of which are better * avoided by the source server; It is best to keep it as read-only to the db/jnl as possible. It is ok to do * so because the source server anyways does not rely on the integrity of the database cache and so does not need * to fix it right away. Any other process that does rely on the cache integrity will fix it when it gets crit next. */ if (is_src_server) return; save_reg = gv_cur_region; /* protect against [at least] M LOCK code which doesn't maintain cs_addrs and cs_data */ TP_CHANGE_REG(reg); /* which are needed by called routines such as wcs_wtstart and wcs_mm_recover */ if (dba_mm == reg->dyn.addr->acc_meth) /* MM uses wcs_recover to remap the database in case of a file extension */ { wcs_mm_recover(reg); TP_CHANGE_REG(save_reg); TREF(wcs_recover_done) = TRUE; return; } csa = &FILE_INFO(reg)->s_addrs; csd = csa->hdr; cnl = csa->nl; si = csa->sgm_info_ptr; /* If a mupip truncate operation was abruptly interrupted we have to correct any inconsistencies */ GTM_TRUNCATE_ONLY(recover_truncate(csa, csd, gv_cur_region);) /* We are going to UNPIN (reset in_cw_set to 0) ALL cache-records so assert that we are not in the middle of a * non-TP or TP transaction that has already PINNED a few buffers as otherwise we will create an out-of-design state. * The only exception is if we are in the 2nd phase of KILL in a TP transaction. In this case si->cr_aray_index * could be non-zero as it is reset only in tp_clean_up which is invoked AFTER freeing up ALL the blocks in * the function gvcst_expand_free_subtree. Work around this by checking for si->kip_csa to be NON NULL in this case. */ assert((!dollar_tlevel && !cr_array_index) || (dollar_tlevel && (!si->cr_array_index || (NULL != si->kip_csa)))); /* We should never invoke wcs_recover in the final retry as that could cause the transaction in progress to restart * (which is an out-of-design situation). There are a few exceptions e.g. tp_restart/t_retry where we have not started * the transaction so allow those. Such places set the variable ok_to_call_wcs_recover to TRUE. Also if we are in * the process of exiting, we are guaranteed no transaction is in progress so it is ok to invoke wcs_recover * even if the variable ok_to_call_wcs_recover is not set to TRUE. */ assert((CDB_STAGNATE > t_tries) || TREF(ok_to_call_wcs_recover) || process_exiting); assert(csa->now_crit || csd->clustered); CHECK_TN(csa, csd, csd->trans_hist.curr_tn); /* can issue rts_error TNTOOLARGE */ SIGNAL_WRITERS_TO_STOP(cnl); /* to stop all active writers */ WAIT_FOR_WRITERS_TO_STOP(cnl, lcnt, MAXWTSTARTWAIT); /* if the wait loop above hits the limit, or cnl->intent_wtstart goes negative, it is ok to proceed since * wcs_verify (invoked below) reports and clears cnl->intent_wtstart and cnl->in_wtstart. */ assert(!TREF(donot_write_inctn_in_wcs_recover) || in_mu_rndwn_file UNIX_ONLY(|| jgbl.onlnrlbk) || jgbl.mur_extract); assert(!in_mu_rndwn_file || (0 == cnl->wcs_phase2_commit_pidcnt)); assert(!csa->wcs_pidcnt_incremented); /* we should never have come here with a phase2 commit pending for ourself */ /* Wait for any pending phase2 commits to finish */ if (cnl->wcs_phase2_commit_pidcnt) { wcs_phase2_commit_wait(csa, NULL); /* not checking return value since even if it fails we want to do recovery */ /* At this point we expect cnl->wcs_phase2_commit_pidcnt to be 0. But it is possible in case of crash tests that * it is non-zero (e.g. in VMS, if the only GT.M process accessing the db was STOP/IDed while in the * DECR_WCS_PHASE2_COMMIT_PIDCNT macro just after resetting csa->wcs_pidcnt_incremented to FALSE but just BEFORE * decrementing cnl->wcs_phase2_commit_pidcnt). Anyways wcs_verify reports and clears this field so no problems. */ } BG_TRACE_PRO_ANY(csa, wc_blocked_wcs_recover_invoked); if (wcs_verify(reg, TRUE, TRUE)) /* expect_damage is TRUE, in_wcs_recover is TRUE */ { /* if it passes verify, then recover can't help ??? what to do */ BG_TRACE_PRO_ANY(csa, wc_blocked_wcs_verify_passed); send_msg(VARLSTCNT(4) ERR_DBCNTRLERR, 2, DB_LEN_STR(reg)); } if (gtmDebugLevel) verifyAllocatedStorage(); change_bmm = FALSE; /* Before recovering the cache, set early_tn to curr_tn + 1 to indicate to have_crit that we are in a situation that * is equivalent to being in the midst of a database commit and therefore defer exit handling in case of a MUPIP STOP. * wc_blocked is anyways set to TRUE at this point so the next process to grab crit will anyways attempt another recovery. */ if (!TREF(donot_write_inctn_in_wcs_recover)) csd->trans_hist.early_tn = csd->trans_hist.curr_tn + 1; assert(!in_wcs_recover); /* should not be called if we are already in "wcs_recover" for another region */ in_wcs_recover = TRUE; /* used by bt_put() called below */ bt_refresh(csa, TRUE); /* this resets all bt->cache_index links to CR_NOTVALID */ /* the following queue head initializations depend on the wc_blocked mechanism for protection from wcs_wtstart */ wip_head = &csa->acc_meth.bg.cache_state->cacheq_wip; memset(wip_head, 0, SIZEOF(cache_que_head)); active_head = &csa->acc_meth.bg.cache_state->cacheq_active; memset(active_head, 0, SIZEOF(cache_que_head)); UNIX_ONLY(wip_head = active_head); /* all inserts into wip_que in VMS should be done in active_que in UNIX */ UNIX_ONLY(SET_LATCH_GLOBAL(&active_head->latch, LOCK_AVAILABLE)); cnl->wcs_active_lvl = 0; cnl->wc_in_free = 0; bplmap = csd->bplmap; hash_hdr = (cache_rec_ptr_t)csa->acc_meth.bg.cache_state->cache_array; bt_buckets = csd->bt_buckets; for (cr = hash_hdr, cr_top = cr + bt_buckets; cr < cr_top; cr++) cr->blkque.fl = cr->blkque.bl = 0; /* take no chances that the blkques are messed up */ cr_lo = cr_top; cr_top = cr_top + csd->n_bts; blk_size = csd->blk_size; buffptr = (sm_uc_ptr_t)ROUND_UP((sm_ulong_t)cr_top, OS_PAGE_SIZE); backup_block_saved = FALSE; if (BACKUP_NOT_IN_PROGRESS != cnl->nbb) { /* Online backup is in progress. Check if secshr_db_clnup has created any cache-records with pointers * to before-images that need to be backed up. If so take care of that first before doing any cache recovery. */ bp_lo = (INTPTR_T)buffptr; bp_top = bp_lo + ((gtm_uint64_t)csd->n_bts * csd->blk_size); for (cr = cr_lo; cr < cr_top; cr++) { if (cr->stopped && (0 != cr->twin)) { /* Check if cr->twin points to a valid buffer. Only in that case, do the backup */ old_block = (INTPTR_T)GDS_ANY_REL2ABS(csa, cr->twin); if (!IS_PTR_IN_RANGE(old_block, bp_lo, bp_top)) { send_msg(VARLSTCNT(11) ERR_DBADDRANGE, 9, DB_LEN_STR(reg), cr, cr->blk, old_block, RTS_ERROR_TEXT("bkup_before_image_range"), bp_lo, bp_top); assert(FALSE); continue; } else if (!IS_PTR_ALIGNED(old_block, bp_lo, csd->blk_size)) { send_msg(VARLSTCNT(11) ERR_DBADDRALIGN, 9, DB_LEN_STR(reg), cr, cr->blk, RTS_ERROR_TEXT("bkup_before_image_align"), old_block, bp_lo, csd->blk_size); assert(FALSE); continue; } bufindx = (int4)((old_block - bp_lo) / csd->blk_size); assert(0 <= bufindx); assert(bufindx < csd->n_bts); cr_alt = &cr_lo[bufindx]; assert((sm_uc_ptr_t)old_block == (sm_uc_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr)); /* Do other checks to validate before-image buffer */ if (cr_alt == cr) { send_msg(VARLSTCNT(13) ERR_DBCRERR, 11, DB_LEN_STR(reg), cr, cr->blk, RTS_ERROR_TEXT("bkup_before_image_cr_same"), cr_alt, FALSE, CALLFROM); assert(FALSE); continue; } else if (cr->blk != cr_alt->blk) { send_msg(VARLSTCNT(13) ERR_DBCRERR, 11, DB_LEN_STR(reg), cr, cr->blk, RTS_ERROR_TEXT("bkup_before_image_blk"), cr_alt->blk, cr->blk, CALLFROM); assert(FALSE); continue; } else if (!cr_alt->in_cw_set) { send_msg(VARLSTCNT(13) ERR_DBCRERR, 11, DB_LEN_STR(reg), cr_alt, cr_alt->blk, RTS_ERROR_TEXT("bkup_before_image_in_cw_set"), cr_alt->in_cw_set, TRUE, CALLFROM); assert(FALSE); continue; } else if (cr_alt->stopped) { send_msg(VARLSTCNT(13) ERR_DBCRERR, 11, DB_LEN_STR(reg), cr_alt, cr_alt->blk, RTS_ERROR_TEXT("bkup_before_image_stopped"), cr_alt->stopped, FALSE, CALLFROM); assert(FALSE); continue; } VMS_ONLY( /* At this point, it is possible cr_alt points to the older twin. In this case though, the * commit should have errored out BEFORE the newer twin got built. This way we are * guaranteed that the cache-record holding the proper before-image is indeed the older * twin. This is asserted below. */ DEBUG_ONLY( cr_alt_new = (cr_alt->twin) ? ((cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->twin)) : NULL; ) assert(!cr_alt->twin || cr_alt->bt_index || cr_alt_new->bt_index && cr_alt_new->in_tend && cr_alt_new->in_cw_set); ) /* The following check is similar to the one in BG_BACKUP_BLOCK and the one in * secshr_db_clnup (where backup_block is invoked) */ blk_ptr = (blk_hdr_ptr_t)old_block; if ((cr_alt->blk >= cnl->nbb) && (0 == csa->shmpool_buffer->failed) && (blk_ptr->tn < csa->shmpool_buffer->backup_tn) && (blk_ptr->tn >= csa->shmpool_buffer->inc_backup_tn)) { cr_alt->buffaddr = cr->twin; /* reset it to what it should be just to be safe */ backup_block(csa, cr_alt->blk, cr_alt, NULL); backup_block_saved = TRUE; } } } } /* After recovering the cache, we normally increment the db curr_tn. But this should not be done if called from * forward journal recovery, since we want the final database transaction number to match the journal file's * eov_tn (to avoid JNLDBTNNOMATCH errors). Therefore in this case, make sure all "tn" fields in the bt and cache are set * to one less than the final db tn. This is done by decrementing the database current transaction number at the * start of the recovery and incrementing it at the end. To keep early_tn and curr_tn in sync, decrement early_tn as well. */ if (!TREF(donot_write_inctn_in_wcs_recover) && mupip_jnl_recover && !JNL_ENABLED(csd)) { csd->trans_hist.curr_tn--; csd->trans_hist.early_tn--; assert(csd->trans_hist.early_tn == (csd->trans_hist.curr_tn + 1)); } for (cr = cr_lo, wait_in_rip = 0; cr < cr_top; cr++, buffptr += blk_size) { cr->buffaddr = GDS_ANY_ABS2REL(csa, buffptr); /* reset it to what it should be just to be safe */ if (((int)(cr->blk) != CR_BLKEMPTY) && (((int)(cr->blk) < 0) || ((int)(cr->blk) >= csd->trans_hist.total_blks))) { /* bad block number. discard buffer for now. actually can do better by looking at cr->bt_index... */ cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; assert(FALSE); SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); } /* fix bad values of cr->dirty and cr->flushed_dirty_tn */ assert(csa->ti == &csd->trans_hist); if (cr->dirty > csd->trans_hist.curr_tn) cr->dirty = csd->trans_hist.curr_tn; /* we assume csd->trans_hist.curr_tn is valid */ if (cr->flushed_dirty_tn >= cr->dirty) cr->flushed_dirty_tn = 0; UNIX_ONLY( /* reset all fields that might be corrupt that wcs_verify() cares about */ cr->epid = 0; cr->image_count = 0; /* image_count needs to be reset before its usage below in case it is corrupt */ ) /* wait for read-in-progress to complete */ for (lcnt = 1; (-1 != cr->read_in_progress); lcnt++) { /* very similar code appears elsewhere and perhaps should be common */ /* Since cr->r_epid can be changing concurrently, take a local copy before using it below, * particularly before calling is_proc_alive as we dont want to call it with a 0 r_epid. */ r_epid = cr->r_epid; if (cr->read_in_progress < -1) { send_msg(VARLSTCNT(4) ERR_INVALIDRIP, 2, DB_LEN_STR(reg)); INTERLOCK_INIT(cr); cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); assert(cr->r_epid == 0); assert(0 == cr->dirty); } else if ((0 != r_epid) && ((r_epid == process_id) || (FALSE == is_proc_alive(r_epid, cr->image_count)))) { INTERLOCK_INIT(cr); /* Process gone, release that process's lock */ cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); } else { if (1 == lcnt) epid = r_epid; else if ((BUF_OWNER_STUCK < lcnt) || (MAX_WAIT_FOR_RIP <= wait_in_rip)) { /* If we have already waited for atleast 4 minutes, no longer wait but fixup * all following cr's. If r_epid is 0 and also read in progress, we identify * this as corruption and fixup up this cr and proceed to the next cr. */ assert(FALSE || (WBTEST_CRASH_SHUTDOWN_EXPECTED == gtm_white_box_test_case_number)); if ((0 != r_epid) && (epid != r_epid)) GTMASSERT; /* process still active but not playing fair or cache is corrupted */ GET_C_STACK_FROM_SCRIPT("BUFRDTIMEOUT", process_id, r_epid, TWICE); send_msg(VARLSTCNT(8) ERR_BUFRDTIMEOUT, 6, process_id, cr->blk, cr, r_epid, DB_LEN_STR(reg)); send_msg(VARLSTCNT(4) ERR_TEXT, 2, LEN_AND_LIT("Buffer forcibly seized")); INTERLOCK_INIT(cr); cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); if (BUF_OWNER_STUCK < lcnt) wait_in_rip++; break; } DEBUG_ONLY( else if (((BUF_OWNER_STUCK / 2) == lcnt) || ((MAX_WAIT_FOR_RIP / 2) == wait_in_rip)) GET_C_STACK_FROM_SCRIPT("BUFRDTIMEOUT", process_id, r_epid, ONCE);) wcs_sleep(lcnt); } } /* reset cr->rip_latch. it is unused in VMS, but wcs_verify() checks it hence the reset in both Unix and VMS */ UNIX_ONLY(SET_LATCH_GLOBAL(&(cr->rip_latch), LOCK_AVAILABLE)); VMS_ONLY(memset((sm_uc_ptr_t)&cr->rip_latch, 0, SIZEOF(global_latch_t))); cr->r_epid = 0; /* the processing above should make this appropriate */ cr->tn = csd->trans_hist.curr_tn; cr->blkque.fl = cr->blkque.bl = 0; /* take no chances that the blkques are messed up */ cr->state_que.fl = cr->state_que.bl = 0; /* take no chances that the state_ques are messed up */ cr->in_cw_set = 0; /* this has crit and is here, so in_cw_set must no longer be non-zero */ UNIX_ONLY(cr->wip_stopped = FALSE;) VMS_ONLY( if (cr->wip_stopped) { for (lcnt = 1; (0 == cr->iosb.cond) && is_proc_alive(cr->epid, cr->image_count); lcnt++) { if (1 == lcnt) epid = cr->epid; else if (BUF_OWNER_STUCK < lcnt) { if ((0 != cr->epid) && (epid != cr->epid)) GTMASSERT; if (0 != epid) { /* process still active, but not playing fair */ send_msg(VARLSTCNT(5) ERR_STOPTIMEOUT, 3, epid, DB_LEN_STR(reg)); send_msg(VARLSTCNT(4) ERR_TEXT, 2, LEN_AND_LIT("Buffer forcibly seized")); cr->epid = 0; } continue; } wcs_sleep(lcnt); } if (0 == cr->iosb.cond) { /* if it's abandonned wip_stopped, treat it as a WRT_STRT_PNDNG */ cr->iosb.cond = WRT_STRT_PNDNG; cr->epid = 0; cr->image_count = 0; } /* otherwise the iosb.cond should suffice */ cr->wip_stopped = FALSE; } ) if (0 != cr->twin) { /* clean up any old twins. in unix twin is unused so reset it without examining its value */ VMS_ONLY( cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr->twin); if (!CR_NOT_ALIGNED(cr_alt, cr_lo) && !CR_NOT_IN_RANGE(cr_alt, cr_lo, cr_top)) { assert(((cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->twin)) == cr); assert((0 == cr->bt_index) || (0 == cr_alt->bt_index)); /* at least one zero */ assert((0 != cr->bt_index) || (0 != cr_alt->bt_index)); /* at least one non-zero */ cr_alt->twin = 0; } ) cr->twin = 0; } if (JNL_ENABLED(csd) && cr->dirty) { if ((NULL != csa->jnl) && (NULL != csa->jnl->jnl_buff) && (cr->jnl_addr > csa->jnl->jnl_buff->freeaddr)) cr->jnl_addr = csa->jnl->jnl_buff->freeaddr; } else cr->jnl_addr = 0; /* just be safe */ assert(!cr->stopped || (CR_BLKEMPTY != cr->blk)); if (cr->stopped && (CR_BLKEMPTY != cr->blk)) { /* cache record attached to a buffer built by secshr_db_clnup: finish work; clearest case: do it 1st */ assert(LATCH_CLEAR == WRITE_LATCH_VAL(cr)); if ((cr->blk / bplmap) * bplmap == cr->blk) { /* it's a bitmap */ if ((csd->trans_hist.total_blks / bplmap) * bplmap == cr->blk) total_blks = csd->trans_hist.total_blks - cr->blk; else total_blks = bplmap; bml_full = bml_find_free(0, (sm_uc_ptr_t)(GDS_ANY_REL2ABS(csa, cr->buffaddr)) + SIZEOF(blk_hdr), total_blks); if (NO_FREE_SPACE == bml_full) { bit_clear(cr->blk / bplmap, MM_ADDR(csd)); if (cr->blk > cnl->highest_lbm_blk_changed) cnl->highest_lbm_blk_changed = cr->blk; change_bmm = TRUE; } else if (!(bit_set(cr->blk / bplmap, MM_ADDR(csd)))) { if (cr->blk > cnl->highest_lbm_blk_changed) cnl->highest_lbm_blk_changed = cr->blk; change_bmm = TRUE; } } /* end of bitmap processing */ if (certify_all_blocks) cert_blk(reg, cr->blk, (blk_hdr_ptr_t)GDS_REL2ABS(cr->buffaddr), 0, TRUE); /* GTMASSERT on error */ bt = bt_put(reg, cr->blk); if (NULL == bt) /* NULL value is only possible if wcs_get_space in bt_put fails */ GTMASSERT; /* That is impossible here since we have called bt_refresh above */ bt->killtn = csd->trans_hist.curr_tn; /* be safe; don't know when was last kill after recover */ if (CR_NOTVALID != bt->cache_index) { /* the bt already identifies another cache entry with this block */ cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, bt->cache_index); assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn >= ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); assert((bt_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->bt_index) == bt); cr_alt->bt_index = 0; /* cr is more recent */ assert(LATCH_CLEAR <= WRITE_LATCH_VAL(cr_alt) && LATCH_CONFLICT >= WRITE_LATCH_VAL(cr_alt)); if (UNIX_ONLY(FALSE &&) LATCH_CLEAR < WRITE_LATCH_VAL(cr_alt)) { /* the previous entry is of interest to some process and therefore must be WIP: * twin and make this (cr->stopped) cache record the active one */ assert(0 == cr_alt->twin); cr->twin = GDS_ANY_ABS2REL(csa, cr_alt); cr_alt->twin = GDS_ANY_ABS2REL(csa, cr); WRITE_LATCH_VAL(cr_alt) = LATCH_CONFLICT; /* semaphore state of a wip twin */ } else { /* the other copy is less recent and not WIP, so discard it */ if ((cr_alt < cr) && cr_alt->state_que.fl) { /* cr_alt has already been processed and is in the state_que. hence remove it */ wq = (cache_que_head_ptr_t)((sm_uc_ptr_t)&cr_alt->state_que + cr_alt->state_que.fl); assert(0 == (((UINTPTR_T)wq) % SIZEOF(cr_alt->state_que.fl))); assert((UINTPTR_T)wq + wq->bl == (UINTPTR_T)&cr_alt->state_que); back_link = (que_ent_ptr_t)remqt((que_ent_ptr_t)wq); assert(EMPTY_QUEUE != back_link); SUB_ENT_FROM_ACTIVE_QUE_CNT(&cnl->wcs_active_lvl, &cnl->wc_var_lock); assert(0 <= cnl->wcs_active_lvl); assert(back_link == (que_ent *)&cr_alt->state_que); /* Now that back_link is out of the active queue, reset its links to 0. * The queue operation functions (see gtm_relqueopi.c) and Unix wcs_get_space * rely on this to determine if an element is IN the queue or not. */ back_link->fl = 0; back_link->bl = 0; } UNIX_ONLY(assert(!cr_alt->twin)); UNIX_ONLY(cr_alt->twin = 0;) cr->twin = cr_alt->twin; /* existing cache record may have a twin */ cr_alt->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr_alt->blk = CR_BLKEMPTY; cr_alt->dirty = 0; cr_alt->flushed_dirty_tn = 0; cr_alt->in_tend = 0; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr_alt); WRITE_LATCH_VAL(cr_alt) = LATCH_CLEAR; VMS_ONLY(cr_alt->iosb.cond = 0;) cr_alt->jnl_addr = 0; cr_alt->refer = FALSE; cr_alt->twin = 0; cnl->wc_in_free++; UNIX_ONLY(assert(!cr->twin)); if (0 != cr->twin) { /* inherited a WIP twin from cr_alt, transfer the twin's affections */ cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr->twin); assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn > ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); assert(LATCH_CONFLICT == WRITE_LATCH_VAL(cr_alt)); /* semaphore for wip twin */ assert(0 == cr_alt->bt_index); cr_alt->twin = GDS_ANY_ABS2REL(csa, cr); } } /* if (LATCH_CLEAR < WRITE_LATCH_VAL(cr_alt)) */ } /* if (CR_NOTVALID == cr_alt) */ bt->cache_index = (int4)GDS_ANY_ABS2REL(csa, cr); cr->bt_index = GDS_ANY_ABS2REL(csa, bt); cr->dirty = csd->trans_hist.curr_tn; cr->flushed_dirty_tn = 0; /* need to be less than cr->dirty. we choose 0. */ cr->epid = 0; cr->image_count = 0; cr->in_tend = 0; cr->data_invalid = 0; WRITE_LATCH_VAL(cr) = LATCH_CLEAR; VMS_ONLY(assert(0 == cr->iosb.cond)); VMS_ONLY(cr->iosb.cond = 0;) cr->refer = TRUE; cr->stopped = FALSE; hq = (cache_que_head_ptr_t)(hash_hdr + (cr->blk % bt_buckets)); insqh((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); insqt((que_ent_ptr_t)&cr->state_que, (que_ent_ptr_t)active_head); ADD_ENT_TO_ACTIVE_QUE_CNT(&cnl->wcs_active_lvl, &cnl->wc_var_lock); continue; } #if defined(DEBUG) && defined(UNIX) if (gtm_white_box_test_case_enabled && (WBTEST_ANTIFREEZE_DBDANGER == gtm_white_box_test_case_number)) { gtm_wbox_input_test_case_count++; /* 50 has no special meaning. Just to trigger this somewhere in the middle once. */ if (50 == gtm_wbox_input_test_case_count) { cr->blk = 0; cr->dirty = 1; cr->data_invalid = 1; } } #endif if ((CR_BLKEMPTY == cr->blk) || (0 == cr->dirty) VMS_ONLY(|| ((0 != cr->iosb.cond) && (0 == cr->bt_index)))) { /* cache record has no valid buffer attached, or its contents are in the database, * or it has a more recent twin so we don't even have to care how its write terminated */ cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; cr->bt_index = 0; cr->data_invalid = 0; cr->dirty = 0; cr->flushed_dirty_tn = 0; cr->epid = 0; cr->image_count = 0; cr->in_tend = 0; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); WRITE_LATCH_VAL(cr) = LATCH_CLEAR; VMS_ONLY(cr->iosb.cond = 0;) cr->jnl_addr = 0; cr->refer = FALSE; cr->stopped = FALSE; /* reset cr->stopped just in case it has a corrupt value */ cnl->wc_in_free++; continue; } if (cr->data_invalid) { /* Some process was shot (kill -9 in Unix and STOP/ID in VMS) in the middle of an update. * In VMS, the kernel extension routine secshr_db_clnup would have rebuilt the block nevertheless. * In Unix, no rebuild would have been attempted since no kernel extension routine currently available. * In either case, we do not want to discard this buffer so send a warning to the user and proceed. */ send_msg(VARLSTCNT(7) ERR_DBDANGER, 5, cr->data_invalid, cr->data_invalid, DB_LEN_STR(reg), cr->blk); cr->data_invalid = 0; } if (cr->in_tend) { /* caught by a failure while in bg_update, and less recent than a cache record created by secshr_db_clnup */ if (UNIX_ONLY(FALSE &&) (LATCH_CONFLICT == WRITE_LATCH_VAL(cr)) VMS_ONLY( && (0 == cr->iosb.cond))) { /* must be WIP, with a currently active write */ assert(LATCH_CONFLICT >= WRITE_LATCH_VAL(cr)); hq = (cache_que_head_ptr_t)(hash_hdr + (cr->blk % bt_buckets)); WRITE_LATCH_VAL(cr) = LATCH_SET; bt = bt_put(reg, cr->blk); if (NULL == bt) /* NULL value is only possible if wcs_get_space in bt_put fails */ GTMASSERT; /* That is impossible here since we have called bt_refresh above */ bt->killtn = csd->trans_hist.curr_tn; /* be safe; don't know when was last kill after recover */ if (CR_NOTVALID == bt->cache_index) { /* no previous entry for this block; more recent cache record will twin when processed */ cr->bt_index = GDS_ANY_ABS2REL(csa, bt); bt->cache_index = (int4)GDS_ANY_ABS2REL(csa, cr); insqh((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); } else { /* form the twin with the previous (and more recent) cache record */ cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, bt->cache_index); assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn < ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); assert((bt_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->bt_index) == bt); assert(0 == cr_alt->twin); cr_alt->twin = GDS_ANY_ABS2REL(csa, cr); cr->twin = GDS_ANY_ABS2REL(csa, cr_alt); cr->bt_index = 0; insqt((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); } assert(cr->epid); /* before inserting into WIP queue, ensure there is a writer process for this */ insqt((que_ent_ptr_t)&cr->state_que, (que_ent_ptr_t)wip_head); /* this should be VMS only code */ } else { /* the [current] in_tend cache record is no longer of value and can be discarded */ cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; cr->bt_index = 0; cr->dirty = 0; cr->flushed_dirty_tn = 0; cr->epid = 0; cr->image_count = 0; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); WRITE_LATCH_VAL(cr) = LATCH_CLEAR; VMS_ONLY(cr->iosb.cond = 0;) cr->jnl_addr = 0; cnl->wc_in_free++; } cr->in_tend = 0; cr->refer = FALSE; continue; } if ((LATCH_SET > WRITE_LATCH_VAL(cr)) VMS_ONLY(|| (WRT_STRT_PNDNG == cr->iosb.cond))) { /* no process has an interest */ bt = bt_put(reg, cr->blk); if (NULL == bt) /* NULL value is only possible if wcs_get_space in bt_put fails */ GTMASSERT; /* That is impossible here since we have called bt_refresh above */ bt->killtn = csd->trans_hist.curr_tn; /* be safe; don't know when was last kill after recover */ if (CR_NOTVALID == bt->cache_index) { /* no previous entry for this block */ bt->cache_index = (int4)GDS_ANY_ABS2REL(csa, cr); cr->bt_index = GDS_ANY_ABS2REL(csa, bt); cr->refer = TRUE; hq = (cache_que_head_ptr_t)(hash_hdr + (cr->blk % bt_buckets)); insqh((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); insqt((que_ent_ptr_t)&cr->state_que, (que_ent_ptr_t)active_head); ADD_ENT_TO_ACTIVE_QUE_CNT(&cnl->wcs_active_lvl, &cnl->wc_var_lock); } else { /* the bt already has an entry for the block */ cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, bt->cache_index); assert((bt_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->bt_index) == bt); if (UNIX_ONLY(FALSE &&) LATCH_CLEAR < WRITE_LATCH_VAL(cr_alt)) { /* the previous cache record is WIP, and the current cache record is the more recent twin */ assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn > ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); VMS_ONLY(assert(WRT_STRT_PNDNG != cr->iosb.cond)); cr_alt->bt_index = 0; WRITE_LATCH_VAL(cr_alt) = LATCH_CONFLICT; cr_alt->twin = GDS_ANY_ABS2REL(csa, cr); cr->twin = GDS_ANY_ABS2REL(csa, cr_alt); bt->cache_index = (int4)GDS_ANY_ABS2REL(csa, cr); cr->bt_index = GDS_ANY_ABS2REL(csa, bt); cr->refer = TRUE; hq = (cache_que_head_ptr_t)(hash_hdr + (cr->blk % bt_buckets)); insqh((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); insqt((que_ent_ptr_t)&cr->state_que, (que_ent_ptr_t)active_head); ADD_ENT_TO_ACTIVE_QUE_CNT(&cnl->wcs_active_lvl, &cnl->wc_var_lock); } else { /* previous cache record is more recent from a cr->stopped record made by sechsr_db_clnup: * discard this copy as it is old */ assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn <= ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); assert(LATCH_CLEAR == WRITE_LATCH_VAL(cr_alt)); cr->cycle++; /* increment cycle whenever blk number changes (tp_hist depends on this) */ cr->blk = CR_BLKEMPTY; cr->bt_index = 0; cr->dirty = 0; cr->flushed_dirty_tn = 0; cr->jnl_addr = 0; cr->refer = FALSE; SHMPOOL_FREE_CR_RFMT_BLOCK(reg, csa, cr); cnl->wc_in_free++; } } cr->epid = 0; cr->image_count = 0; WRITE_LATCH_VAL(cr) = LATCH_CLEAR; VMS_ONLY(assert(0 == cr->iosb.cond || WRT_STRT_PNDNG == cr->iosb.cond)); VMS_ONLY(cr->iosb.cond = 0;) continue; } /* not in_tend and interlock.semaphore is not LATCH_CLEAR so cache record must be WIP */ assert(LATCH_CONFLICT >= WRITE_LATCH_VAL(cr)); VMS_ONLY(WRITE_LATCH_VAL(cr) = LATCH_SET;) UNIX_ONLY(WRITE_LATCH_VAL(cr) = LATCH_CLEAR;) hq = (cache_que_head_ptr_t)(hash_hdr + (cr->blk % bt_buckets)); bt = bt_put(reg, cr->blk); if (NULL == bt) /* NULL value is only possible if wcs_get_space in bt_put fails */ GTMASSERT; /* That is impossible here since we have called bt_refresh above */ bt->killtn = csd->trans_hist.curr_tn; /* be safe; don't know when was last kill after recover */ if (CR_NOTVALID == bt->cache_index) { /* no previous entry for this block */ bt->cache_index = (int4)GDS_ANY_ABS2REL(csa, cr); cr->bt_index = GDS_ANY_ABS2REL(csa, bt); cr->refer = TRUE; insqh((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); } else { /* previous cache record must be more recent as this one is WIP */ cr_alt = (cache_rec_ptr_t)GDS_ANY_REL2ABS(csa, bt->cache_index); assert(((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr->buffaddr))->tn < ((blk_hdr_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->buffaddr))->tn); assert((bt_rec_ptr_t)GDS_ANY_REL2ABS(csa, cr_alt->bt_index) == bt); VMS_ONLY( assert(WRT_STRT_PNDNG != cr->iosb.cond); assert(FALSE == cr_alt->wip_stopped); WRITE_LATCH_VAL(cr) = LATCH_CONFLICT; cr_alt->twin = GDS_ANY_ABS2REL(csa, cr); cr->twin = GDS_ANY_ABS2REL(csa, cr_alt); ) cr->bt_index = 0; cr->refer = FALSE; insqt((que_ent_ptr_t)&cr->blkque, (que_ent_ptr_t)hq); } VMS_ONLY(assert(cr->epid)); /* before inserting into WIP queue, ensure there is a writer process for this */ insqt((que_ent_ptr_t)&cr->state_que, (que_ent_ptr_t)wip_head); UNIX_ONLY(ADD_ENT_TO_ACTIVE_QUE_CNT(&cnl->wcs_active_lvl, &cnl->wc_var_lock)); /* end of processing for a single cache record */ } /* end of processing all cache records */ if (change_bmm) { csd->trans_hist.mm_tn++; if (!reg->read_only) fileheader_sync(reg); } if (FALSE == wcs_verify(reg, FALSE, TRUE)) /* expect_damage is FALSE, in_wcs_recover is TRUE */ GTMASSERT; /* skip INCTN processing in case called from mu_rndwn_file(). * if called from mu_rndwn_file(), we have standalone access to shared memory so no need to increment db curr_tn * or write inctn (since no concurrent GT.M process is present in order to restart because of this curr_tn change) */ if (!TREF(donot_write_inctn_in_wcs_recover)) { jpc = csa->jnl; if (JNL_ENABLED(csd) && (NULL != jpc) && (NULL != jpc->jnl_buff)) { assert(&FILE_INFO(jpc->region)->s_addrs == csa); if (!jgbl.dont_reset_gbl_jrec_time) { SET_GBL_JREC_TIME; /* needed for jnl_ensure_open, jnl_put_jrt_pini and jnl_write_inctn_rec */ } assert(jgbl.gbl_jrec_time); jbp = jpc->jnl_buff; /* Before writing to jnlfile, adjust jgbl.gbl_jrec_time if needed to maintain time order * of jnl records. This needs to be done BEFORE the jnl_ensure_open as that could write * journal records (if it decides to switch to a new journal file). */ ADJUST_GBL_JREC_TIME(jgbl, jbp); jnl_status = jnl_ensure_open(); if (0 == jnl_status) { if (0 == jpc->pini_addr) jnl_put_jrt_pini(csa); save_inctn_opcode = inctn_opcode; /* in case caller does not expect inctn_opcode to be changed here */ inctn_opcode = inctn_wcs_recover; jnl_write_inctn_rec(csa); inctn_opcode = save_inctn_opcode; } else jnl_file_lost(jpc, jnl_status); } INCREMENT_CURR_TN(csd); } csa->wbuf_dqd = 0; /* reset this so the wcs_wtstart below will work */ SIGNAL_WRITERS_TO_RESUME(cnl); in_wcs_recover = FALSE; if (!reg->read_only) { DCLAST_WCS_WTSTART(reg, 0, dummy_errno); VMS_ONLY( wcs_wtfini(gv_cur_region); /* try to free as many buffers from the wip queue if write is done */ ) } if (backup_block_saved) backup_buffer_flush(reg); TP_CHANGE_REG(save_reg); TREF(wcs_recover_done) = TRUE; return; } #ifdef UNIX #ifdef MM_FILE_EXT_OK void wcs_mm_recover(gd_region *reg) { int mm_prot; INTPTR_T status; struct stat stat_buf; sm_uc_ptr_t old_base[2]; sigset_t savemask; boolean_t need_to_restore_mask = FALSE, was_crit; unix_db_info *udi; assert(&FILE_INFO(reg)->s_addrs == cs_addrs); assert(cs_addrs->hdr == cs_data); if (!(was_crit = cs_addrs->now_crit) && !(cs_addrs->hdr->clustered)) grab_crit(gv_cur_region); SET_TRACEABLE_VAR(cs_addrs->nl->wc_blocked, FALSE); if (cs_addrs->total_blks == cs_addrs->ti->total_blks) { /* I am the one who actually did the extension, don't need to remap again */ if (!was_crit) rel_crit(gv_cur_region); return; } mm_prot = cs_addrs->read_write ? (PROT_READ | PROT_WRITE) : PROT_READ; /* Block SIGALRM to ensure cs_data and cs_addrs are always in-sync / No IO in this period */ sigprocmask(SIG_BLOCK, &blockalrm, &savemask); old_base[0] = cs_addrs->db_addrs[0]; old_base[1] = cs_addrs->db_addrs[1]; status = (INTPTR_T)munmap((caddr_t)old_base[0], (size_t)(old_base[1] - old_base[0])); if (-1 != status) { udi = FILE_INFO(gv_cur_region); FSTAT_FILE(udi->fd, &stat_buf, status); status = (sm_long_t)(cs_addrs->db_addrs[0] = (sm_uc_ptr_t)mmap((caddr_t)NULL, (size_t)stat_buf.st_size, mm_prot, GTM_MM_FLAGS, udi->fd, (off_t)0)); } if (-1 == status) { sigprocmask(SIG_SETMASK, &savemask, NULL); if (!was_crit) rel_crit(gv_cur_region); rts_error(VARLSTCNT(5) ERR_DBFILERR, 2, DB_LEN_STR(reg), errno); } /* In addition to updating the internal map values, gds_map_moved also updates cs_data to point to the remapped file */ gds_map_moved(cs_addrs->db_addrs[0], old_base[0], old_base[1], (off_t)stat_buf.st_size); cs_addrs->total_blks = cs_addrs->ti->total_blks; if (!was_crit) rel_crit(gv_cur_region); sigprocmask(SIG_SETMASK, &savemask, NULL); return; } #else /* !MM_FILE_EXT_OK */ void wcs_mm_recover(gd_region *reg) { unsigned char *end, buff[MAX_ZWR_KEY_SZ]; if (NULL == (end = format_targ_key(buff, MAX_ZWR_KEY_SZ, gv_currkey, TRUE))) end = &buff[MAX_ZWR_KEY_SZ - 1]; rts_error(VARLSTCNT(6) ERR_GBLOFLOW, 0, ERR_GVIS, 2, end - buff, buff); return; } #endif #elif defined(VMS) /* wcs_mm_recover is not yet implemented on VMS */ void wcs_mm_recover(gd_region *reg) { unsigned char *end, buff[MAX_ZWR_KEY_SZ]; assert(&FILE_INFO(reg)->s_addrs == cs_addrs); assert(cs_addrs->now_crit); assert(cs_addrs->hdr == cs_data); if (!cs_addrs->hold_onto_crit) rel_crit(gv_cur_region); if (NULL == (end = format_targ_key(buff, MAX_ZWR_KEY_SZ, gv_currkey, TRUE))) end = &buff[MAX_ZWR_KEY_SZ - 1]; rts_error(VARLSTCNT(6) ERR_GBLOFLOW, 0, ERR_GVIS, 2, end - buff, buff); return; } #else # error UNSUPPORTED PLATFORM #endif