394 lines
14 KiB
C
394 lines
14 KiB
C
/****************************************************************
|
|
* *
|
|
* Copyright 2012, 2013 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 <errno.h>
|
|
#include "gtm_stdio.h"
|
|
#include "gtm_ctype.h"
|
|
|
|
#include "gdsroot.h"
|
|
#include "gdsbt.h"
|
|
#include "gdsfhead.h"
|
|
#include "filestruct.h"
|
|
#include "repl_msg.h"
|
|
#include "gtmsource.h"
|
|
#include "error.h"
|
|
#include "repl_sem.h"
|
|
#include "gtmimagename.h"
|
|
#include "hashtab_str.h"
|
|
#include "eintr_wrappers.h"
|
|
#include "gtmmsg.h"
|
|
#include "anticipatory_freeze.h"
|
|
#ifdef DEBUG
|
|
#include "dpgbldir.h"
|
|
#include "is_proc_alive.h"
|
|
#endif
|
|
|
|
#define MAX_TAG_LEN 128 /* Maximum size of an error mnemonic */
|
|
#define MAX_READ_SZ 1024 /* Mnemonic + flags shouldn't exceed this limit */
|
|
#define COMMENT_DELIMITER ';'
|
|
#define NEWLINE 0x0A
|
|
#define EOL_REACHED (char *)(-1)
|
|
#define EOF_REACHED (char *)(-2)
|
|
|
|
#define EXHAUST_CURRENT_LINE(BUFF, HANDLE, FGETS_RC) \
|
|
{ \
|
|
assert(NEWLINE != BUFF[STRLEN(BUFF) - 1]); \
|
|
while (TRUE) \
|
|
{ \
|
|
FGETS_FILE(BUFF, MAX_READ_SZ, HANDLE, FGETS_RC); \
|
|
if ((NULL == FGETS_RC) || NEWLINE == BUFF[STRLEN(BUFF) - 1]) \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
error_def(ERR_ASSERT);
|
|
error_def(ERR_CUSTERRNOTFND);
|
|
error_def(ERR_CUSTERRSYNTAX);
|
|
error_def(ERR_CUSTOMFILOPERR);
|
|
error_def(ERR_DSKSPCAVAILABLE);
|
|
error_def(ERR_ENOSPCQIODEFER);
|
|
error_def(ERR_REPLINSTFREEZECOMMENT);
|
|
error_def(ERR_REPLINSTFROZEN);
|
|
error_def(ERR_TEXT);
|
|
error_def(ERR_INSTFRZDEFER);
|
|
|
|
GBLREF jnlpool_addrs jnlpool;
|
|
GBLREF jnlpool_ctl_ptr_t jnlpool_ctl;
|
|
GBLREF boolean_t is_src_server;
|
|
GBLREF boolean_t holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS];
|
|
#ifdef DEBUG
|
|
GBLREF uint4 process_id;
|
|
GBLREF volatile boolean_t timer_in_handler;
|
|
#endif
|
|
|
|
/* Typically prototypes are included in the header file. But, in this case the static function - get_mnemonic_offset - has the
|
|
* hash_table_str as one of the function parameters which means all the files which includes anticipatory_freeze.h needs to include
|
|
* hashtab_str.h and since there are lot of such C files, we chose to define static function prototypes in the C file itself.
|
|
*/
|
|
STATICFNDCL char *scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top);
|
|
STATICFNDCL int get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len);
|
|
|
|
/* Scan through whitespace in the current buffer (read more if required) */
|
|
STATICFNDEF char *scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top)
|
|
{
|
|
char *fgets_rc;
|
|
|
|
while (TRUE)
|
|
{
|
|
for (; (buffptr < buff_top) && (ISSPACE_ASCII(*buffptr)); buffptr++)
|
|
;
|
|
if (buffptr < buff_top)
|
|
return buffptr; /* first non-whitespace character */
|
|
if (NEWLINE == *(buffptr - 1))
|
|
return EOL_REACHED;
|
|
/* current buffer is exhausted and we haven't seen a newline; read more */
|
|
FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc);
|
|
if (NULL == fgets_rc)
|
|
break;
|
|
buffptr = buff;
|
|
buff_top = buffptr + STRLEN(buff);
|
|
}
|
|
return EOF_REACHED;
|
|
}
|
|
|
|
STATICFNDEF int get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len)
|
|
{
|
|
const err_msg *msg_beg, *msg_top;
|
|
hash_table_str *tmp_err_hashtab;
|
|
ht_ent_str *err_htent;
|
|
stringkey key;
|
|
err_msg *msg_info;
|
|
boolean_t added;
|
|
DEBUG_ONLY(int idx;)
|
|
|
|
msg_beg = merrors_ctl.fst_msg;
|
|
msg_top = msg_beg + merrors_ctl.msg_cnt;
|
|
assert('\0' == mnemonic_buf[mnemonic_len]);
|
|
if (NULL == (tmp_err_hashtab = *err_hashtab))
|
|
{ /* create and populate hash-table for future lookups */
|
|
tmp_err_hashtab = (hash_table_str *)malloc(SIZEOF(hash_table_str));
|
|
DEBUG_ONLY(tmp_err_hashtab->base = NULL);
|
|
init_hashtab_str(tmp_err_hashtab, 0, HASHTAB_NO_COMPACT, HASHTAB_NO_SPARE_TABLE);
|
|
assert(tmp_err_hashtab->base);
|
|
for (msg_info = (err_msg *)msg_beg; msg_info < msg_top; msg_info++)
|
|
{
|
|
key.str.addr = msg_info->tag;
|
|
key.str.len = STRLEN(msg_info->tag);
|
|
COMPUTE_HASH_STR(&key);
|
|
added = add_hashtab_str(tmp_err_hashtab, &key, msg_info, &err_htent);
|
|
assert(added);
|
|
assert(err_htent->value);
|
|
assert(msg_info->tag == ((err_msg *)(err_htent->value))->tag);
|
|
}
|
|
*err_hashtab = tmp_err_hashtab;
|
|
}
|
|
assert(NULL != tmp_err_hashtab);
|
|
/* lookup for the mnemonic */
|
|
key.str.addr = mnemonic_buf;
|
|
key.str.len = mnemonic_len;
|
|
COMPUTE_HASH_STR(&key);
|
|
if (NULL == (err_htent = lookup_hashtab_str(tmp_err_hashtab, &key)))
|
|
return -1;
|
|
msg_info = (err_msg *)(err_htent->value);
|
|
assert(msg_info >= msg_beg && msg_info < msg_top);
|
|
return msg_info - msg_beg;
|
|
}
|
|
|
|
/* Determine whether a given msg_id qualifies for an anticipatory freeze or not */
|
|
boolean_t is_anticipatory_freeze_needed(sgmnt_addrs *csa, int msg_id)
|
|
{
|
|
const err_ctl *ctl;
|
|
int idx;
|
|
|
|
assert(NULL != jnlpool.jnlpool_ctl);
|
|
/* Certain error messages should NOT trigger a freeze even if they are so configured in the custom errors file as they might
|
|
* result in instance freezes that can be set indefinitely. Currently, we know of at least 3 such messages:
|
|
* 1. ENOSPCQIODEFER and INSTFRZDEFER : To ensure we don't set anticipatory freeze if we don't/can't hold crit
|
|
* (due to possible deadlock)
|
|
* 2. DSKSPCAVAILABLE : To ensure we don't set anticipatory freeze if the disk space becomes available after an initial
|
|
* lack of space.
|
|
* These messages have csa == NULL so they are guarranteed to not trigger a freeze.
|
|
*/
|
|
|
|
assert(((ERR_ENOSPCQIODEFER != msg_id) && (ERR_DSKSPCAVAILABLE != msg_id) && (ERR_INSTFRZDEFER != msg_id))
|
|
|| (NULL == csa));
|
|
if (!csa || !csa->nl || !csa->hdr || !csa->hdr->freeze_on_fail)
|
|
return FALSE;
|
|
ctl = err_check(msg_id);
|
|
if (NULL != ctl)
|
|
{
|
|
GET_MSG_IDX(msg_id, ctl, idx);
|
|
assert(idx < ARRAYSIZE(jnlpool_ctl->merrors_array));
|
|
if (jnlpool_ctl->merrors_array[idx] & AFREEZE_MASK)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* set the anticipatory freeze in the journal pool */
|
|
void set_anticipatory_freeze(sgmnt_addrs *csa, int msg_id)
|
|
{
|
|
boolean_t was_crit;
|
|
sgmnt_addrs *repl_csa;
|
|
const err_msg *msginfo;
|
|
# ifdef DEBUG
|
|
qw_off_t write_addr;
|
|
uint4 write;
|
|
# endif
|
|
|
|
assert(is_anticipatory_freeze_needed(csa, msg_id));
|
|
DEBUG_ONLY(
|
|
write_addr = jnlpool_ctl->write_addr;
|
|
write = jnlpool_ctl->write;
|
|
)
|
|
assert(write == write_addr % jnlpool_ctl->jnlpool_size);
|
|
repl_csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs;
|
|
assert(NULL != repl_csa);
|
|
was_crit = repl_csa->now_crit;
|
|
if (!was_crit)
|
|
{
|
|
if (csa->now_crit)
|
|
grab_lock(jnlpool.jnlpool_dummy_reg, TRUE, GRAB_LOCK_ONLY);
|
|
else if (FALSE == grab_lock(jnlpool.jnlpool_dummy_reg, FALSE, GRAB_LOCK_ONLY))
|
|
{
|
|
MSGID_TO_ERRMSG(msg_id, msginfo);
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_INSTFRZDEFER, 4, LEN_AND_STR(msginfo->tag),
|
|
REG_LEN_STR(csa->region));
|
|
return;
|
|
}
|
|
}
|
|
/* Now that we hold necessary locks, set the freeze and the comment field */
|
|
jnlpool.jnlpool_ctl->freeze = TRUE;
|
|
GENERATE_INST_FROZEN_COMMENT(jnlpool.jnlpool_ctl->freeze_comment, SIZEOF(jnlpool.jnlpool_ctl->freeze_comment), msg_id);
|
|
/* TODO : Do we need a SHM_WRITE_MEMORY_BARRIER ? */
|
|
if (!was_crit)
|
|
rel_lock(jnlpool.jnlpool_dummy_reg);
|
|
}
|
|
|
|
/* initialize jnlpool_ctl->merrors_array to set up the list of errors that should trigger anticipatory freeze errors */
|
|
boolean_t init_anticipatory_freeze_errors()
|
|
{
|
|
int idx, save_errno, status, mnemonic_len, offset, line_no;
|
|
FILE *handle;
|
|
char *fgets_rc;
|
|
char buff[MAX_READ_SZ], mnemonic_buf[MAX_TAG_LEN];
|
|
char *buffptr, *buff_top, *errptr, *errptr_top;
|
|
mstr custom_err_file;
|
|
hash_table_str *err_hashtab = NULL;
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
/* TODO : Currently, we process errors that belong to merrors[] as those are the ones related to database/journal. Need
|
|
* to check if cmerrors/cmierrors also need to be included in this list or not.
|
|
*/
|
|
assert(IS_MUPIP_IMAGE); /* is_src_server is not initialized at this point */
|
|
assert(jnlpool_ctl && !jnlpool_ctl->pool_initialized); /* should be invoked BEFORE the journal pool is fully-initialized */
|
|
assert(holds_sem[SOURCE][JNL_POOL_ACCESS_SEM]); /* should hold journal pool access control semaphore */
|
|
/* Now, read the custom errors file and populate the journal pool */
|
|
custom_err_file = TREF(gtm_custom_errors);
|
|
handle = Fopen(custom_err_file.addr, "r");
|
|
if (NULL == handle)
|
|
{
|
|
save_errno = errno;
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
return FALSE;
|
|
}
|
|
line_no = 0;
|
|
/* The code below parses a custom errors file in the following format.
|
|
*
|
|
* file ::= line*
|
|
* line ::= mnemonic SPACE* comment? EOL |
|
|
* comment EOL
|
|
* mnemonic ::= ALNUM+
|
|
* comment ::= COMMENT_DELIMITER ANY*
|
|
*
|
|
* SPACE ::= any ASCII white space character
|
|
* COMMENT_DELIMITER ::= ';'
|
|
* ANY ::= any ASCII character except end of line
|
|
* EOL ::= ASCII end of line character
|
|
* ALNUM ::= any ASCII alphanumeric character
|
|
*
|
|
* NOTES:
|
|
* "*" denotes zero-or-more of the previous item
|
|
* "?" denotes zero-or-one of the previous item
|
|
* "+" denotes one-or-more of the previous item
|
|
* "|" denotes multiple alternatives
|
|
* The mnemonic must match an entry in the GT.M error message list.
|
|
* Anything between the COMMENT_DELIMITER and EOL is ignored.
|
|
* Each iteration of the loop parses one line.
|
|
*/
|
|
while (TRUE)
|
|
{
|
|
FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc);
|
|
line_no++;
|
|
if (NULL == fgets_rc)
|
|
break;
|
|
buffptr = buff;
|
|
buff_top = buffptr + STRLEN(buff);
|
|
errptr = &mnemonic_buf[0];
|
|
errptr_top = errptr + MAX_TAG_LEN;
|
|
/* The first character has to be alpha-numeric or a comment */
|
|
if (!ISALNUM_ASCII(*buffptr) && (COMMENT_DELIMITER != *buffptr))
|
|
{
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr,
|
|
line_no, ERR_TEXT, 2,
|
|
LEN_AND_LIT("First character should be comment (;) or alpha numeric"));
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr,
|
|
line_no, ERR_TEXT, 2,
|
|
LEN_AND_LIT("First character should be comment (;) or alpha numeric"));
|
|
return FALSE;
|
|
}
|
|
while (ISALNUM_ASCII(*buffptr))
|
|
{
|
|
*errptr++ = *buffptr++;
|
|
if (errptr > errptr_top)
|
|
{
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len,
|
|
custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long"));
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len,
|
|
custom_err_file.addr, line_no, ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long"));
|
|
return FALSE;
|
|
}
|
|
assert(buffptr < buff_top); /* errptr > errptr_top should fail before this */
|
|
}
|
|
*errptr = '\0';
|
|
if (0 < (mnemonic_len = (errptr - &mnemonic_buf[0])))
|
|
{ /* Non-empty error mnemonic found; look it up */
|
|
if (-1 == (offset = get_mnemonic_offset(&err_hashtab, mnemonic_buf, mnemonic_len)))
|
|
{
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
|
|
return FALSE;
|
|
}
|
|
jnlpool_ctl->merrors_array[offset] |= AFREEZE_MASK; /* duplicate entries are not considered an error */
|
|
}
|
|
assert(ISSPACE_ASCII(*buffptr) || (COMMENT_DELIMITER == *buffptr));
|
|
if (EOL_REACHED == (buffptr = scan_space(handle, buff, buffptr, buff_top)))
|
|
continue;
|
|
else if (EOF_REACHED == buffptr)
|
|
break;
|
|
assert(buffptr < buff_top);
|
|
if (COMMENT_DELIMITER != *buffptr)
|
|
{
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr,
|
|
line_no, ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic"));
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr,
|
|
line_no, ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic"));
|
|
return FALSE;
|
|
}
|
|
/* Need to ignore the rest of the current buffer and exhaust the current line */
|
|
if (NEWLINE != *(buff_top - 1))
|
|
EXHAUST_CURRENT_LINE(buff, handle, fgets_rc);
|
|
}
|
|
if (err_hashtab)
|
|
{
|
|
free_hashtab_str(err_hashtab);
|
|
free(err_hashtab);
|
|
}
|
|
if (!feof(handle))
|
|
{
|
|
save_errno = errno;
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
return FALSE;
|
|
}
|
|
FCLOSE(handle, status);
|
|
if (SS_NORMAL != status)
|
|
{
|
|
save_errno = errno;
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len,
|
|
custom_err_file.addr, save_errno);
|
|
return FALSE;
|
|
}
|
|
jnlpool_ctl->instfreeze_environ_inited = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void clear_fake_enospc_if_master_dead(void)
|
|
{
|
|
gd_addr *addr_ptr;
|
|
gd_region *r_top, *r_local;
|
|
sgmnt_addrs *csa;
|
|
|
|
if((jnlpool_ctl->jnlpool_creator_pid != process_id) && !is_proc_alive(jnlpool_ctl->jnlpool_creator_pid, 0))
|
|
{
|
|
for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
|
|
{
|
|
for (r_local = addr_ptr->regions, r_top = r_local + addr_ptr->n_regions; r_local < r_top; r_local++)
|
|
{
|
|
if ((dba_bg != r_local->dyn.addr->acc_meth) && (dba_mm != r_local->dyn.addr->acc_meth))
|
|
continue;
|
|
csa = REG2CSA(r_local);
|
|
if ((NULL != csa) && (NULL != csa->nl))
|
|
if (csa->nl->fake_db_enospc || csa->nl->fake_jnl_enospc)
|
|
{
|
|
csa->nl->fake_db_enospc = FALSE;
|
|
csa->nl->fake_jnl_enospc = FALSE;
|
|
send_msg_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_TEXT, 2, DB_LEN_STR(r_local), ERR_TEXT,
|
|
2, LEN_AND_LIT("Resetting fake_db_enospc and fake_jnl_enospc because "
|
|
"fake ENOSPC master is dead"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|