aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/error/elog.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/error/elog.c')
-rw-r--r--src/backend/utils/error/elog.c391
1 files changed, 267 insertions, 124 deletions
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index d2f9117059c..3eeee5ed8b2 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -37,14 +37,13 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.143 2004/07/28 22:05:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.144 2004/07/31 00:45:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <fcntl.h>
-#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
@@ -67,6 +66,8 @@
/* Global variables */
ErrorContextCallback *error_context_stack = NULL;
+sigjmp_buf *PG_exception_stack = NULL;
+
/* GUC parameters */
PGErrorVerbosity Log_error_verbosity = PGERROR_VERBOSE;
char *Log_line_prefix = NULL; /* format for extra log line info */
@@ -82,33 +83,6 @@ static void write_syslog(int level, const char *line);
static void write_eventlog(int level, const char *line);
#endif
-/*
- * ErrorData holds the data accumulated during any one ereport() cycle.
- * Any non-NULL pointers must point to palloc'd data in ErrorContext.
- * (The const pointers are an exception; we assume they point at non-freeable
- * constant strings.)
- */
-
-typedef struct ErrorData
-{
- int elevel; /* error level */
- bool output_to_server; /* will report to server log? */
- bool output_to_client; /* will report to client? */
- bool show_funcname; /* true to force funcname inclusion */
- const char *filename; /* __FILE__ of ereport() call */
- int lineno; /* __LINE__ of ereport() call */
- const char *funcname; /* __func__ of ereport() call */
- int sqlerrcode; /* encoded ERRSTATE */
- char *message; /* primary error message */
- char *detail; /* detail error message */
- char *hint; /* hint message */
- char *context; /* context message */
- int cursorpos; /* cursor index into query string */
- int internalpos; /* cursor index into internalquery */
- char *internalquery; /* text of internally-generated query */
- int saved_errno; /* errno at entry */
-} ErrorData;
-
/* We provide a small stack of ErrorData records for re-entrant cases */
#define ERRORDATA_STACK_SIZE 5
@@ -166,7 +140,7 @@ errstart(int elevel, const char *filename, int lineno,
/*
* Convert initialization errors into fatal errors. This is probably
- * redundant, because Warn_restart_ready won't be set anyway.
+ * redundant, because PG_exception_stack will still be null anyway.
*/
if (elevel == ERROR && IsInitProcessingMode())
elevel = FATAL;
@@ -257,20 +231,13 @@ errstart(int elevel, const char *filename, int lineno,
}
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
- /* Wups, stack not big enough */
- int i;
-
- elevel = Max(elevel, ERROR);
-
/*
- * Don't forget any FATAL/PANIC status on the stack (see comments
- * in errfinish)
+ * Wups, stack not big enough. We treat this as a PANIC condition
+ * because it suggests an infinite loop of errors during error
+ * recovery.
*/
- for (i = 0; i < errordata_stack_depth; i++)
- elevel = Max(elevel, errordata[i].elevel);
- /* Clear the stack and try again */
- errordata_stack_depth = -1;
- ereport(elevel, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
+ errordata_stack_depth = -1; /* make room on stack */
+ ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
/* Initialize data for this error frame */
@@ -331,23 +298,90 @@ errfinish(int dummy,...)
econtext = econtext->previous)
(*econtext->callback) (econtext->arg);
- /* Send to server log, if enabled */
- if (edata->output_to_server)
- send_message_to_server_log(edata);
+ /*
+ * If the error level is ERROR or more, we are not going to return to
+ * caller; therefore, if there is any stacked error already in
+ * progress it will be lost. This is more or less okay, except we do
+ * not want to have a FATAL or PANIC error downgraded because the
+ * reporting process was interrupted by a lower-grade error. So check
+ * the stack and make sure we panic if panic is warranted.
+ */
+ if (elevel >= ERROR)
+ {
+ int i;
+
+ for (i = 0; i <= errordata_stack_depth; i++)
+ elevel = Max(elevel, errordata[i].elevel);
+ }
+
+ /*
+ * Check some other reasons for treating ERROR as FATAL:
+ *
+ * 1. we have no handler to pass the error to (implies we are in
+ * the postmaster or in backend startup).
+ *
+ * 2. ExitOnAnyError mode switch is set (initdb uses this).
+ *
+ * 3. the error occurred after proc_exit has begun to run. (It's
+ * proc_exit's responsibility to see that this doesn't turn into
+ * infinite recursion!)
+ */
+ if (elevel == ERROR)
+ {
+ if (PG_exception_stack == NULL ||
+ ExitOnAnyError ||
+ proc_exit_inprogress)
+ elevel = FATAL;
+ else
+ {
+ /*
+ * Otherwise we can pass the error off to the current handler.
+ * Printing it and popping the stack is the responsibility of
+ * the handler.
+ *
+ * We do some minimal cleanup before longjmp'ing so that handlers
+ * can execute in a reasonably sane state.
+ */
+
+ /* This is just in case the error came while waiting for input */
+ ImmediateInterruptOK = false;
+
+ /*
+ * Reset InterruptHoldoffCount in case we ereport'd from inside an
+ * interrupt holdoff section. (We assume here that no handler
+ * will itself be inside a holdoff section. If necessary, such
+ * a handler could save and restore InterruptHoldoffCount for
+ * itself, but this should make life easier for most.)
+ */
+ InterruptHoldoffCount = 0;
+
+ CritSectionCount = 0; /* should be unnecessary, but... */
+
+ /*
+ * Note that we leave CurrentMemoryContext set to ErrorContext.
+ * The handler should reset it to something else soon.
+ */
+
+ recursion_depth--;
+ PG_RE_THROW();
+ }
+ }
/*
- * Abort any old-style COPY OUT in progress when an error is detected.
+ * If we are doing FATAL or PANIC, abort any old-style COPY OUT in
+ * progress, so that we can report the message before dying. (Without
+ * this, pq_putmessage will refuse to send the message at all, which
+ * is what we want for NOTICE messages, but not for fatal exits.)
* This hack is necessary because of poor design of old-style copy
* protocol. Note we must do this even if client is fool enough to
- * have set client_min_messages above ERROR, so don't look at
+ * have set client_min_messages above FATAL, so don't look at
* output_to_client.
*/
- if (elevel >= ERROR && whereToSendOutput == Remote)
+ if (elevel >= FATAL && whereToSendOutput == Remote)
pq_endcopyout(true);
- /* Send to client, if enabled */
- if (edata->output_to_client)
- send_message_to_frontend(edata);
+ /* Emit the message to the right places */
+ EmitErrorReport();
/* Now free up subsidiary data attached to stack entry, and release it */
if (edata->message)
@@ -361,41 +395,20 @@ errfinish(int dummy,...)
if (edata->internalquery)
pfree(edata->internalquery);
- MemoryContextSwitchTo(oldcontext);
-
errordata_stack_depth--;
+
+ /* Exit error-handling context */
+ MemoryContextSwitchTo(oldcontext);
recursion_depth--;
/*
- * If the error level is ERROR or more, we are not going to return to
- * caller; therefore, if there is any stacked error already in
- * progress it will be lost. This is more or less okay, except we do
- * not want to have a FATAL or PANIC error downgraded because the
- * reporting process was interrupted by a lower-grade error. So check
- * the stack and make sure we panic if panic is warranted.
+ * Perform error recovery action as specified by elevel.
*/
- if (elevel >= ERROR)
+ if (elevel == FATAL)
{
- int i;
-
- for (i = 0; i <= errordata_stack_depth; i++)
- elevel = Max(elevel, errordata[i].elevel);
-
/*
- * Also, be sure to reset the stack to empty. We do not clear
- * ErrorContext here, though; PostgresMain does that later on.
+ * For a FATAL error, we let proc_exit clean up and exit.
*/
- errordata_stack_depth = -1;
- recursion_depth = 0;
- error_context_stack = NULL;
- }
-
- /*
- * Perform error recovery action as specified by elevel.
- */
- if (elevel == ERROR || elevel == FATAL)
- {
- /* Prevent immediate interrupt while entering error recovery */
ImmediateInterruptOK = false;
/*
@@ -403,59 +416,27 @@ errfinish(int dummy,...)
* disconnect on receiving it, so don't send any more to the
* client.
*/
- if (!Warn_restart_ready && whereToSendOutput == Remote)
+ if (PG_exception_stack == NULL && whereToSendOutput == Remote)
whereToSendOutput = None;
/*
- * For a FATAL error, we let proc_exit clean up and exit.
- *
- * There are several other cases in which we treat ERROR as FATAL and
- * go directly to proc_exit:
- *
- * 1. ExitOnAnyError mode switch is set (initdb uses this).
- *
- * 2. we have not yet entered the main backend loop (ie, we are in
- * the postmaster or in backend startup); we have noplace to
- * recover.
- *
- * 3. the error occurred after proc_exit has begun to run. (It's
- * proc_exit's responsibility to see that this doesn't turn into
- * infinite recursion!)
- *
- * In the last case, we exit with nonzero exit code to indicate that
- * something's pretty wrong. We also want to exit with nonzero
- * exit code if not running under the postmaster (for example, if
- * we are being run from the initdb script, we'd better return an
- * error status).
- */
- if (elevel == FATAL ||
- ExitOnAnyError ||
- !Warn_restart_ready ||
- proc_exit_inprogress)
- {
- /*
- * fflush here is just to improve the odds that we get to see
- * the error message, in case things are so hosed that
- * proc_exit crashes. Any other code you might be tempted to
- * add here should probably be in an on_proc_exit callback
- * instead.
- */
- fflush(stdout);
- fflush(stderr);
- proc_exit(proc_exit_inprogress || !IsUnderPostmaster);
- }
-
- /*
- * Guard against infinite loop from errors during error recovery.
+ * fflush here is just to improve the odds that we get to see
+ * the error message, in case things are so hosed that
+ * proc_exit crashes. Any other code you might be tempted to
+ * add here should probably be in an on_proc_exit callback
+ * instead.
*/
- if (InError)
- ereport(PANIC, (errmsg("error during error recovery, giving up")));
- InError = true;
+ fflush(stdout);
+ fflush(stderr);
/*
- * Otherwise we can return to the main loop in postgres.c.
+ * If proc_exit is already running, we exit with nonzero exit code to
+ * indicate that something's pretty wrong. We also want to exit with
+ * nonzero exit code if not running under the postmaster (for example,
+ * if we are being run from the initdb script, we'd better return an
+ * error status).
*/
- siglongjmp(Warn_restart, 1);
+ proc_exit(proc_exit_inprogress || !IsUnderPostmaster);
}
if (elevel >= PANIC)
@@ -923,6 +904,168 @@ elog_finish(int elevel, const char *fmt,...)
errfinish(0);
}
+/*
+ * Actual output of the top-of-stack error message
+ *
+ * In the ereport(ERROR) case this is called from PostgresMain (or not at all,
+ * if the error is caught by somebody). For all other severity levels this
+ * is called by errfinish.
+ */
+void
+EmitErrorReport(void)
+{
+ ErrorData *edata = &errordata[errordata_stack_depth];
+ MemoryContext oldcontext;
+
+ recursion_depth++;
+ CHECK_STACK_DEPTH();
+ oldcontext = MemoryContextSwitchTo(ErrorContext);
+
+ /* Send to server log, if enabled */
+ if (edata->output_to_server)
+ send_message_to_server_log(edata);
+
+ /* Send to client, if enabled */
+ if (edata->output_to_client)
+ send_message_to_frontend(edata);
+
+ MemoryContextSwitchTo(oldcontext);
+ recursion_depth--;
+}
+
+/*
+ * CopyErrorData --- obtain a copy of the topmost error stack entry
+ *
+ * This is only for use in error handler code. The data is copied into the
+ * current memory context, so callers should always switch away from
+ * ErrorContext first; otherwise it will be lost when FlushErrorState is done.
+ */
+ErrorData *
+CopyErrorData(void)
+{
+ ErrorData *edata = &errordata[errordata_stack_depth];
+ ErrorData *newedata;
+
+ /*
+ * we don't increment recursion_depth because out-of-memory here does
+ * not indicate a problem within the error subsystem.
+ */
+ CHECK_STACK_DEPTH();
+
+ Assert(CurrentMemoryContext != ErrorContext);
+
+ /* Copy the struct itself */
+ newedata = (ErrorData *) palloc(sizeof(ErrorData));
+ memcpy(newedata, edata, sizeof(ErrorData));
+
+ /* Make copies of separately-allocated fields */
+ if (newedata->message)
+ newedata->message = pstrdup(newedata->message);
+ if (newedata->detail)
+ newedata->detail = pstrdup(newedata->detail);
+ if (newedata->hint)
+ newedata->hint = pstrdup(newedata->hint);
+ if (newedata->context)
+ newedata->context = pstrdup(newedata->context);
+ if (newedata->internalquery)
+ newedata->internalquery = pstrdup(newedata->internalquery);
+
+ return newedata;
+}
+
+/*
+ * FreeErrorData --- free the structure returned by CopyErrorData.
+ *
+ * Error handlers should use this in preference to assuming they know all
+ * the separately-allocated fields.
+ */
+void
+FreeErrorData(ErrorData *edata)
+{
+ if (edata->message)
+ pfree(edata->message);
+ if (edata->detail)
+ pfree(edata->detail);
+ if (edata->hint)
+ pfree(edata->hint);
+ if (edata->context)
+ pfree(edata->context);
+ if (edata->internalquery)
+ pfree(edata->internalquery);
+ pfree(edata);
+}
+
+/*
+ * FlushErrorState --- flush the error state after error recovery
+ *
+ * This should be called by an error handler after it's done processing
+ * the error; or as soon as it's done CopyErrorData, if it intends to
+ * do stuff that is likely to provoke another error. You are not "out" of
+ * the error subsystem until you have done this.
+ */
+void
+FlushErrorState(void)
+{
+ /*
+ * Reset stack to empty. The only case where it would be more than
+ * one deep is if we serviced an error that interrupted construction
+ * of another message. We assume control escaped out of that
+ * message construction and won't ever go back.
+ */
+ errordata_stack_depth = -1;
+ recursion_depth = 0;
+ /* Delete all data in ErrorContext */
+ MemoryContextResetAndDeleteChildren(ErrorContext);
+}
+
+/*
+ * ReThrowError --- re-throw a previously copied error
+ *
+ * A handler can do CopyErrorData/FlushErrorState to get out of the error
+ * subsystem, then do some processing, and finally ReThrowError to re-throw
+ * the original error. This is slower than just PG_RE_THROW() but should
+ * be used if the "some processing" is likely to incur another error.
+ */
+void
+ReThrowError(ErrorData *edata)
+{
+ ErrorData *newedata;
+
+ Assert(edata->elevel == ERROR);
+
+ /* Push the data back into the error context */
+ recursion_depth++;
+ MemoryContextSwitchTo(ErrorContext);
+
+ if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
+ {
+ /*
+ * Wups, stack not big enough. We treat this as a PANIC condition
+ * because it suggests an infinite loop of errors during error
+ * recovery.
+ */
+ errordata_stack_depth = -1; /* make room on stack */
+ ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
+ }
+
+ newedata = &errordata[errordata_stack_depth];
+ memcpy(newedata, edata, sizeof(ErrorData));
+
+ /* Make copies of separately-allocated fields */
+ if (newedata->message)
+ newedata->message = pstrdup(newedata->message);
+ if (newedata->detail)
+ newedata->detail = pstrdup(newedata->detail);
+ if (newedata->hint)
+ newedata->hint = pstrdup(newedata->hint);
+ if (newedata->context)
+ newedata->context = pstrdup(newedata->context);
+ if (newedata->internalquery)
+ newedata->internalquery = pstrdup(newedata->internalquery);
+
+ recursion_depth--;
+ PG_RE_THROW();
+}
/*
* Initialization of error output file