6e5ee11ca95cd971984038cf65bae00d9c898707 galt Wed Jun 4 15:40:02 2014 -0700 Since we have git, it is easy to rename errabort.c to errAbort.c without losing any history. diff --git src/lib/errAbort.c src/lib/errAbort.c new file mode 100644 index 0000000..ccfa56e --- /dev/null +++ src/lib/errAbort.c @@ -0,0 +1,374 @@ +/* errAbort.c - our error handler. + * + * This maintains two stacks - a warning message printer + * stack, and a "abort handler" stack. + * + * Note that the abort function always calls the warn handler first. + * This is so that the message gets sent. + * + * By default the warnings will go to stderr, and + * aborts will exit the program. You can push a + * function on to the appropriate stack to change + * this behavior. The top function on the stack + * gets called. + * + * This file is copyright 2002 Jim Kent, but license is hereby + * granted for all use - public, private or commercial. */ + +// developer: this include is for an occasionally useful means of getting stack info without +// crashing +// however, it is not supported on cygwin. Conditionally compile this in when desired. +//#define BACKTRACE_EXISTS +#ifdef BACKTRACE_EXISTS +#include <execinfo.h> +#endif///def BACKTRACE_EXISTS +#include <pthread.h> +#include "common.h" +#include "hash.h" +#include "dystring.h" +#include "errAbort.h" + + + +#define maxWarnHandlers 20 +#define maxAbortHandlers 12 +struct perThreadAbortVars +/* per thread variables for abort and warn */ + { + boolean debugPushPopErr; // generate stack dump on push/pop error + boolean errAbortInProgress; /* Flag to indicate that an error abort is in progress. + * Needed so that a warn handler can tell if it's really + * being called because of a warning or an error. */ + WarnHandler warnArray[maxWarnHandlers]; + int warnIx; + AbortHandler abortArray[maxAbortHandlers]; + int abortIx; + }; + +static struct perThreadAbortVars *getThreadVars(); // forward declaration + +static void defaultVaWarn(char *format, va_list args) +/* Default error message handler. */ +{ +if (format != NULL) { + fflush(stdout); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +static void silentVaWarn(char *format, va_list args) +/* Warning handler that just hides it. Useful sometimes when high level code + * expects low level code may fail (as in finding a file on the net) but doesn't + * want user to be bothered about it. */ +{ +} + + +void vaWarn(char *format, va_list args) +/* Call top of warning stack to issue warning. */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +ptav->warnArray[ptav->warnIx](format, args); +} + +void warn(char *format, ...) +/* Issue a warning message. */ +{ +va_list args; +va_start(args, format); +vaWarn(format, args); +va_end(args); +} + +void warnWithBackTrace(char *format, ...) +/* Issue a warning message and append backtrace. */ +{ +va_list args; +va_start(args, format); +struct dyString *dy = newDyString(255); +dyStringAppend(dy, format); + +#define STACK_LIMIT 20 +char **strings = NULL; +int count = 0; + +// developer: this is an occasionally useful means of getting stack info without crashing +// however, it is not supported on cygwin. Conditionally compile this in when desired. +// The define is at top to include execinfo.h +#ifdef BACKTRACE_EXISTS +void *buffer[STACK_LIMIT]; +count = backtrace(buffer, STACK_LIMIT); +strings = backtrace_symbols(buffer, count); +#endif///def BACKTRACE_EXISTS + +if (strings == NULL) + dyStringAppend(dy,"\nno backtrace_symbols available in errabort::warnWithBackTrace()."); +else + { + int ix = 1; + dyStringAppend(dy,"\nBACKTRACE (use on cmdLine):"); + if (strings[1] != NULL) + { + strSwapChar(strings[1],' ','\0'); + dyStringPrintf(dy,"\naddr2line -Cfise %s",strings[1]); + strings[1] += strlen(strings[1]) + 1; + } + for (; ix < count && strings[ix] != NULL; ix++) + { + strings[ix] = skipBeyondDelimit(strings[ix],'['); + strSwapChar(strings[ix],']','\0'); + dyStringPrintf(dy," %s",strings[ix]); + } + + free(strings); + } +vaWarn(dyStringCannibalize(&dy), args); +va_end(args); +} + + +void errnoWarn(char *format, ...) +/* Prints error message from UNIX errno first, then does rest of warning. */ +{ +char fbuf[512]; +va_list args; +va_start(args, format); +sprintf(fbuf, "%s\n%s", strerror(errno), format); +vaWarn(fbuf, args); +va_end(args); +} + + +void pushWarnHandler(WarnHandler handler) +/* Set abort handler */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +if (ptav->warnIx >= maxWarnHandlers-1) + { + if (ptav->debugPushPopErr) + dumpStack("pushWarnHandler overflow"); + errAbort("Too many pushWarnHandlers, can only handle %d\n", maxWarnHandlers-1); + } +ptav->warnArray[++ptav->warnIx] = handler; +} + +void popWarnHandler() +/* Revert to old warn handler. */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +if (ptav->warnIx <= 0) + { + if (ptav->debugPushPopErr) + dumpStack("popWarnHandler underflow"); + errAbort("Too few popWarnHandlers"); + } +--ptav->warnIx; +} + +static void defaultAbort() +/* Default error handler exits program. */ +{ +if ((getenv("ERRASSERT") != NULL) || (getenv("ERRABORT") != NULL)) + abort(); +else + exit(-1); +} + + +void noWarnAbort() +/* Abort without message. */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +ptav->abortArray[ptav->abortIx](); +exit(-1); /* This is just to make compiler happy. + * We have already exited or longjmped by now. */ +} + +void vaErrAbort(char *format, va_list args) +/* Abort function, with optional (vprintf formatted) error message. */ +{ +/* flag is needed because both errAbort and warn generate message + * using the warn handler, however sometimes one needed to know + * (like when logging), if it's an error or a warning. This is far from + * perfect, as this isn't cleared if the error handler continues, + * as with an exception mechanism. */ +struct perThreadAbortVars *ptav = getThreadVars(); +ptav->errAbortInProgress = TRUE; +vaWarn(format, args); +noWarnAbort(); +} + +void errAbort(char *format, ...) +/* Abort function, with optional (printf formatted) error message. */ +{ +va_list args; +va_start(args, format); +vaErrAbort(format, args); +va_end(args); +} + +void errnoAbort(char *format, ...) +/* Prints error message from UNIX errno first, then does errAbort. */ +{ +char fbuf[512]; +va_list args; +va_start(args, format); +sprintf(fbuf, "%s\n%s", strerror(errno), format); +vaErrAbort(fbuf, args); +va_end(args); +} + +void pushAbortHandler(AbortHandler handler) +/* Set abort handler */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +if (ptav->abortIx >= maxAbortHandlers-1) + { + if (ptav->debugPushPopErr) + dumpStack("pushAbortHandler overflow"); + errAbort("Too many pushAbortHandlers, can only handle %d", maxAbortHandlers-1); + } +ptav->abortArray[++ptav->abortIx] = handler; +} + +void popAbortHandler() +/* Revert to old abort handler. */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +if (ptav->abortIx <= 0) + { + if (ptav->debugPushPopErr) + dumpStack("popAbortHandler underflow"); + errAbort("Too many popAbortHandlers\n"); + } +--ptav->abortIx; +} + +static void debugAbort() +/* Call the debugger. */ +{ +fflush(stdout); +assert(FALSE); +defaultAbort(); +} + +void pushDebugAbort() +/* Push abort handler that will invoke debugger. */ +{ +pushAbortHandler(debugAbort); +} + +static void warnAbortHandler(char *format, va_list args) +/* warn handler that also aborts. */ +{ +defaultVaWarn(format, args); +noWarnAbort(); +} + +void pushWarnAbort() +/* Push handler that will abort on warnings. */ +{ +pushWarnHandler(warnAbortHandler); +} + +void pushSilentWarnHandler() +/* Set warning handler to be quiet. Do a popWarnHandler to restore. */ +{ +pushWarnHandler(silentVaWarn); +} + +void errAbortDebugnPushPopErr() +/* generate stack dump if there is a error in the push/pop functions */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +ptav->debugPushPopErr = TRUE; +} + +boolean isErrAbortInProgress() +/* Flag to indicate that an error abort is in progress. + * Needed so that a warn handler can tell if it's really + * being called because of a warning or an error. */ +{ +struct perThreadAbortVars *ptav = getThreadVars(); +return ptav->errAbortInProgress; +} + + +static struct perThreadAbortVars *getThreadVars() +/* Return a pointer to the perThreadAbortVars for the current pthread. */ +{ +pthread_t pid = pthread_self(); // pthread_t can be a pointer or a number, implementation-dependent. + +// Test for out-of-memory condition causing re-entrancy into this function that would block +// on its own main mutex ptavMutex. Do this by looking for its own pid. +// Some care must be exercised in testing and comparing the threads pid against one in-use. +// We need yet another mutex and a boolean to tell us when the pidInUse value may be safely compared to pid. + +// Use a boolean since there is no known unused value for pthread_t variable. NULL and -1 are not portable. +static boolean pidInUseValid = FALSE; // tells when pidInUse contains a valid pid that can be compared. +static pthread_t pidInUse; // there is no "unused" value to which we can initialize this. +static pthread_mutex_t pidInUseMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_lock( &pidInUseMutex ); +// If this pid equals pidInUse, then this function has been re-entered due to severe out-of-memory error. +// But we only compare them when pidInUseValid is TRUE. +if (pidInUseValid && pthread_equal(pid, pidInUse)) + { + // Avoid deadlock on self by exiting immediately. + // Use pthread_equal because directly comparing two pthread_t vars is not allowed. + // This re-entrancy only happens when it has aborted already due to out of memory + // which should be a rare occurrence. + char *errMsg = "errAbort re-entered due to out-of-memory condition. Exiting.\n"; + write(STDERR_FILENO, errMsg, strlen(errMsg)); + exit(1); // out of memory is a serious problem, exit immediately, but allow atexit cleanup. + } +pthread_mutex_unlock( &pidInUseMutex ); + +// This is the main mutex we really care about. +// It controls access to the hash where thread-specific data is stored. +static pthread_mutex_t ptavMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_lock( &ptavMutex ); + +// safely tell threads that pidInUse +// is valid and correctly set and may be compared to pid +pthread_mutex_lock( &pidInUseMutex ); +pidInUse = pthread_self(); // setting it directly to pid is not allowed. +pidInUseValid = TRUE; +pthread_mutex_unlock( &pidInUseMutex ); + +// This means that if we crash due to out-of-memory below, +// it will be able to detect the re-entrancy and handle it above. + +static struct hash *perThreadVars = NULL; +if (perThreadVars == NULL) + perThreadVars = hashNew(0); +// convert the pid into a string for the hash key +char pidStr[64]; +safef(pidStr, sizeof(pidStr), "%lld", ptrToLL(pid)); +struct hashEl *hel = hashLookup(perThreadVars, pidStr); +if (hel == NULL) + { + // if it is the first time, initialization the perThreadAbortVars + struct perThreadAbortVars *ptav; + AllocVar(ptav); + ptav->debugPushPopErr = FALSE; + ptav->errAbortInProgress = FALSE; + ptav->warnIx = 0; + ptav->warnArray[0] = defaultVaWarn; + ptav->abortIx = 0; + ptav->abortArray[0] = defaultAbort; + hel = hashAdd(perThreadVars, pidStr, ptav); + } + +// safely tell other threads that pidInUse +// is no longer valid and may not be compared to pid +pthread_mutex_lock( &pidInUseMutex ); +pidInUseValid = FALSE; +pthread_mutex_unlock( &pidInUseMutex ); + +// unlock our mutex controlling the hash of thread-specific data +pthread_mutex_unlock( &ptavMutex ); +return (struct perThreadAbortVars *)(hel->val); +} +