diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/bootstrap/bootstrap.c | 12 | ||||
-rw-r--r-- | src/backend/commands/copy.c | 49 | ||||
-rw-r--r-- | src/backend/commands/portalcmds.c | 157 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 109 | ||||
-rw-r--r-- | src/backend/postmaster/bgwriter.c | 43 | ||||
-rw-r--r-- | src/backend/postmaster/postmaster.c | 8 | ||||
-rw-r--r-- | src/backend/storage/large_object/inv_api.c | 46 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 109 | ||||
-rw-r--r-- | src/backend/tcop/pquery.c | 348 | ||||
-rw-r--r-- | src/backend/utils/error/elog.c | 391 | ||||
-rw-r--r-- | src/backend/utils/resowner/README | 6 | ||||
-rw-r--r-- | src/backend/utils/resowner/resowner.c | 37 |
12 files changed, 811 insertions, 504 deletions
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 39887015930..2a4f9a6f503 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.189 2004/07/21 20:34:45 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.190 2004/07/31 00:45:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,7 +16,6 @@ #include <unistd.h> #include <signal.h> -#include <setjmp.h> #ifdef HAVE_GETOPT_H #include <getopt.h> #endif @@ -459,15 +458,6 @@ BootstrapMain(int argc, char *argv[]) hashtable[i] = NULL; /* - * abort processing resumes here (this is probably dead code?) - */ - if (sigsetjmp(Warn_restart, 1) != 0) - { - Warnings++; - AbortCurrentTransaction(); - } - - /* * Process bootstrap input. * * the sed script boot.sed renamed yyparse to Int_yyparse for the diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 4a05b40eea8..8307e9b47d2 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,13 +8,12 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.227 2004/06/16 01:26:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.228 2004/07/31 00:45:31 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" -#include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <netinet/in.h> @@ -130,6 +129,9 @@ static StringInfoData line_buf; static bool line_buf_converted; /* non-export function prototypes */ +static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids, + char *delim, char *null_print, bool csv_mode, char *quote, + char *escape, List *force_quote_atts, bool fe_copy); static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, List *force_quote_atts); @@ -688,6 +690,7 @@ DoCopy(const CopyStmt *stmt) ListCell *option; List *attnamelist = stmt->attlist; List *attnumlist; + bool fe_copy = false; bool binary = false; bool oids = false; bool csv_mode = false; @@ -1062,7 +1065,7 @@ DoCopy(const CopyStmt *stmt) if (pipe) { if (whereToSendOutput == Remote) - SendCopyBegin(binary, list_length(attnumlist)); + fe_copy = true; else copy_file = stdout; } @@ -1099,8 +1102,9 @@ DoCopy(const CopyStmt *stmt) errmsg("\"%s\" is a directory", filename))); } } - CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode, - quote, escape, force_quote_atts); + + DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode, + quote, escape, force_quote_atts, fe_copy); } if (!pipe) @@ -1112,8 +1116,6 @@ DoCopy(const CopyStmt *stmt) errmsg("could not write to file \"%s\": %m", filename))); } - else if (whereToSendOutput == Remote && !is_from) - SendCopyEnd(binary); pfree(attribute_buf.data); pfree(line_buf.data); @@ -1128,6 +1130,39 @@ DoCopy(const CopyStmt *stmt) /* + * This intermediate routine just exists to localize the effects of setjmp + * so we don't need to plaster a lot of variables with "volatile". + */ +static void +DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids, + char *delim, char *null_print, bool csv_mode, char *quote, + char *escape, List *force_quote_atts, bool fe_copy) +{ + PG_TRY(); + { + if (fe_copy) + SendCopyBegin(binary, list_length(attnumlist)); + + CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode, + quote, escape, force_quote_atts); + + if (fe_copy) + SendCopyEnd(binary); + } + PG_CATCH(); + { + /* + * Make sure we turn off old-style COPY OUT mode upon error. + * It is okay to do this in all cases, since it does nothing + * if the mode is not on. + */ + pq_endcopyout(true); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* * Copy from relation TO file. */ static void diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index b176a6c0c7b..509d9e0dfa1 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.29 2004/07/17 03:28:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.30 2004/07/31 00:45:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -259,8 +259,18 @@ PortalCleanup(Portal portal) /* We must make the portal's resource owner current */ saveResourceOwner = CurrentResourceOwner; - CurrentResourceOwner = portal->resowner; - ExecutorEnd(queryDesc); + PG_TRY(); + { + CurrentResourceOwner = portal->resowner; + ExecutorEnd(queryDesc); + } + PG_CATCH(); + { + /* Ensure CurrentResourceOwner is restored on error */ + CurrentResourceOwner = saveResourceOwner; + PG_RE_THROW(); + } + PG_END_TRY(); CurrentResourceOwner = saveResourceOwner; } } @@ -317,86 +327,95 @@ PersistHoldablePortal(Portal portal) portal->status = PORTAL_ACTIVE; /* - * Set global portal context pointers. + * Set up global portal context pointers. */ saveActivePortal = ActivePortal; - ActivePortal = portal; saveResourceOwner = CurrentResourceOwner; - CurrentResourceOwner = portal->resowner; savePortalContext = PortalContext; - PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; - QueryContext = portal->queryContext; + PG_TRY(); + { + ActivePortal = portal; + CurrentResourceOwner = portal->resowner; + PortalContext = PortalGetHeapMemory(portal); + QueryContext = portal->queryContext; + + MemoryContextSwitchTo(PortalContext); + + /* + * Rewind the executor: we need to store the entire result set in the + * tuplestore, so that subsequent backward FETCHs can be processed. + */ + ExecutorRewind(queryDesc); + + /* Change the destination to output to the tuplestore */ + queryDesc->dest = CreateDestReceiver(Tuplestore, portal); + + /* Fetch the result set into the tuplestore */ + ExecutorRun(queryDesc, ForwardScanDirection, 0L); + + (*queryDesc->dest->rDestroy) (queryDesc->dest); + queryDesc->dest = NULL; + + /* + * Now shut down the inner executor. + */ + portal->queryDesc = NULL; /* prevent double shutdown */ + ExecutorEnd(queryDesc); + + /* + * Reset the position in the result set: ideally, this could be + * implemented by just skipping straight to the tuple # that we need + * to be at, but the tuplestore API doesn't support that. So we start + * at the beginning of the tuplestore and iterate through it until we + * reach where we need to be. FIXME someday? + */ + MemoryContextSwitchTo(portal->holdContext); + + if (!portal->atEnd) + { + long store_pos; - MemoryContextSwitchTo(PortalContext); + if (portal->posOverflow) /* oops, cannot trust portalPos */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not reposition held cursor"))); - /* - * Rewind the executor: we need to store the entire result set in the - * tuplestore, so that subsequent backward FETCHs can be processed. - */ - ExecutorRewind(queryDesc); + tuplestore_rescan(portal->holdStore); - /* Change the destination to output to the tuplestore */ - queryDesc->dest = CreateDestReceiver(Tuplestore, portal); + for (store_pos = 0; store_pos < portal->portalPos; store_pos++) + { + HeapTuple tup; + bool should_free; - /* Fetch the result set into the tuplestore */ - ExecutorRun(queryDesc, ForwardScanDirection, 0L); + tup = tuplestore_gettuple(portal->holdStore, true, + &should_free); - (*queryDesc->dest->rDestroy) (queryDesc->dest); - queryDesc->dest = NULL; + if (tup == NULL) + elog(ERROR, "unexpected end of tuple stream"); - /* - * Now shut down the inner executor. - */ - portal->queryDesc = NULL; /* prevent double shutdown */ - ExecutorEnd(queryDesc); - - /* - * Reset the position in the result set: ideally, this could be - * implemented by just skipping straight to the tuple # that we need - * to be at, but the tuplestore API doesn't support that. So we start - * at the beginning of the tuplestore and iterate through it until we - * reach where we need to be. FIXME someday? - */ - MemoryContextSwitchTo(portal->holdContext); - - if (!portal->atEnd) + if (should_free) + pfree(tup); + } + } + } + PG_CATCH(); { - long store_pos; - - if (portal->posOverflow) /* oops, cannot trust portalPos */ - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("could not reposition held cursor"))); - - tuplestore_rescan(portal->holdStore); - - for (store_pos = 0; store_pos < portal->portalPos; store_pos++) - { - HeapTuple tup; - bool should_free; - - tup = tuplestore_gettuple(portal->holdStore, true, - &should_free); + /* Uncaught error while executing portal: mark it dead */ + portal->status = PORTAL_FAILED; - if (tup == NULL) - elog(ERROR, "unexpected end of tuple stream"); + /* Restore global vars and propagate error */ + ActivePortal = saveActivePortal; + CurrentResourceOwner = saveResourceOwner; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; - if (should_free) - pfree(tup); - } + PG_RE_THROW(); } + PG_END_TRY(); MemoryContextSwitchTo(oldcxt); - /* - * We can now release any subsidiary memory of the portal's heap - * context; we'll never use it again. The executor already dropped - * its context, but this will clean up anything that glommed onto the - * portal's heap via PortalContext. - */ - MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); - /* Mark portal not active */ portal->status = PORTAL_READY; @@ -404,4 +423,12 @@ PersistHoldablePortal(Portal portal) CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; QueryContext = saveQueryContext; + + /* + * We can now release any subsidiary memory of the portal's heap + * context; we'll never use it again. The executor already dropped + * its context, but this will clean up anything that glommed onto the + * portal's heap via PortalContext. + */ + MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index dfc03ea461a..23d7996cf53 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.284 2004/07/21 22:31:21 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.285 2004/07/31 00:45:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -228,14 +228,13 @@ void vacuum(VacuumStmt *vacstmt) { const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; - MemoryContext anl_context = NULL; TransactionId initialOldestXmin = InvalidTransactionId; TransactionId initialFreezeLimit = InvalidTransactionId; - bool all_rels, + volatile MemoryContext anl_context = NULL; + volatile bool all_rels, in_outer_xact, use_own_xacts; List *relations; - ListCell *cur; if (vacstmt->verbose) elevel = INFO; @@ -267,10 +266,6 @@ vacuum(VacuumStmt *vacstmt) in_outer_xact = IsInTransactionChain((void *) vacstmt); } - /* Turn vacuum cost accounting on or off */ - VacuumCostActive = (VacuumCostNaptime > 0); - VacuumCostBalance = 0; - /* * Send info about dead objects to the statistics collector */ @@ -377,57 +372,76 @@ vacuum(VacuumStmt *vacstmt) CommitTransactionCommand(); } - /* - * Loop to process each selected relation. - */ - foreach(cur, relations) + /* Turn vacuum cost accounting on or off */ + PG_TRY(); { - Oid relid = lfirst_oid(cur); + ListCell *cur; - if (vacstmt->vacuum) - { - if (!vacuum_rel(relid, vacstmt, RELKIND_RELATION)) - all_rels = false; /* forget about updating dbstats */ - } - if (vacstmt->analyze) + VacuumCostActive = (VacuumCostNaptime > 0); + VacuumCostBalance = 0; + + /* + * Loop to process each selected relation. + */ + foreach(cur, relations) { - MemoryContext old_context = NULL; + Oid relid = lfirst_oid(cur); - /* - * If using separate xacts, start one for analyze. Otherwise, - * we can use the outer transaction, but we still need to call - * analyze_rel in a memory context that will be cleaned up on - * return (else we leak memory while processing multiple - * tables). - */ - if (use_own_xacts) + if (vacstmt->vacuum) { - StartTransactionCommand(); - SetQuerySnapshot(); /* might be needed for functions - * in indexes */ + if (!vacuum_rel(relid, vacstmt, RELKIND_RELATION)) + all_rels = false; /* forget about updating dbstats */ } - else - old_context = MemoryContextSwitchTo(anl_context); + if (vacstmt->analyze) + { + MemoryContext old_context = NULL; - /* - * Tell the buffer replacement strategy that vacuum is - * causing the IO - */ - StrategyHintVacuum(true); + /* + * If using separate xacts, start one for analyze. Otherwise, + * we can use the outer transaction, but we still need to call + * analyze_rel in a memory context that will be cleaned up on + * return (else we leak memory while processing multiple + * tables). + */ + if (use_own_xacts) + { + StartTransactionCommand(); + SetQuerySnapshot(); /* might be needed for functions + * in indexes */ + } + else + old_context = MemoryContextSwitchTo(anl_context); + + /* + * Tell the buffer replacement strategy that vacuum is + * causing the IO + */ + StrategyHintVacuum(true); - analyze_rel(relid, vacstmt); + analyze_rel(relid, vacstmt); - StrategyHintVacuum(false); + StrategyHintVacuum(false); - if (use_own_xacts) - CommitTransactionCommand(); - else - { - MemoryContextSwitchTo(old_context); - MemoryContextResetAndDeleteChildren(anl_context); + if (use_own_xacts) + CommitTransactionCommand(); + else + { + MemoryContextSwitchTo(old_context); + MemoryContextResetAndDeleteChildren(anl_context); + } } } } + PG_CATCH(); + { + /* Make sure cost accounting is turned off after error */ + VacuumCostActive = false; + PG_RE_THROW(); + } + PG_END_TRY(); + + /* Turn off vacuum cost accounting */ + VacuumCostActive = false; /* * Finish up processing. @@ -475,9 +489,6 @@ vacuum(VacuumStmt *vacstmt) if (anl_context) MemoryContextDelete(anl_context); - - /* Turn off vacuum cost accounting */ - VacuumCostActive = false; } /* diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index f0ecb13d27a..0da107f9e13 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/bgwriter.c,v 1.3 2004/06/03 02:08:03 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/bgwriter.c,v 1.4 2004/07/31 00:45:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,6 +57,7 @@ #include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/guc.h" +#include "utils/memutils.h" /*---------- @@ -153,6 +154,8 @@ static void ReqShutdownHandler(SIGNAL_ARGS); void BackgroundWriterMain(void) { + sigjmp_buf local_sigjmp_buf; + Assert(BgWriterShmem != NULL); BgWriterShmem->bgwriter_pid = MyProcPid; am_bg_writer = true; @@ -201,19 +204,19 @@ BackgroundWriterMain(void) /* * If an exception is encountered, processing resumes here. + * + * See notes in postgres.c about the design of this coding. */ - if (sigsetjmp(Warn_restart, 1) != 0) + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - /* - * Make sure we're not interrupted while cleaning up. Also forget - * any pending QueryCancel request, since we're aborting anyway. - * Force InterruptHoldoffCount to a known state in case we - * ereport'd from inside a holdoff section. - */ - ImmediateInterruptOK = false; - QueryCancelPending = false; - InterruptHoldoffCount = 1; - CritSectionCount = 0; /* should be unnecessary, but... */ + /* Since not using PG_TRY, must reset error stack by hand */ + error_context_stack = NULL; + + /* Prevent interrupts while cleaning up */ + HOLD_INTERRUPTS(); + + /* Report the error to the server log */ + EmitErrorReport(); /* * These operations are really just a minimal subset of @@ -224,12 +227,6 @@ BackgroundWriterMain(void) AbortBufferIO(); UnlockBuffers(); - /* - * Clear flag to indicate that we got out of error recovery mode - * successfully. (Flag was set in elog.c before longjmp().) - */ - InError = false; - /* Warn any waiting backends that the checkpoint failed. */ if (ckpt_active) { @@ -242,8 +239,13 @@ BackgroundWriterMain(void) } /* - * Exit interrupt holdoff section we implicitly established above. + * Now return to normal top-level context and clear ErrorContext + * for next time. */ + MemoryContextSwitchTo(TopMemoryContext); + FlushErrorState(); + + /* Now we can allow interrupts again */ RESUME_INTERRUPTS(); /* @@ -255,7 +257,8 @@ BackgroundWriterMain(void) pg_usleep(1000000L); } - Warn_restart_ready = true; /* we can now handle ereport(ERROR) */ + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; /* * Unblock signals (they were blocked when the postmaster forked us) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index eace246071b..e144b6eaa93 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.416 2004/07/27 01:46:03 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.417 2004/07/31 00:45:33 tgl Exp $ * * NOTES * @@ -73,7 +73,6 @@ #include <ctype.h> #include <sys/stat.h> #include <sys/socket.h> -#include <errno.h> #include <fcntl.h> #include <sys/param.h> #include <netinet/in.h> @@ -3226,6 +3225,11 @@ StartChildProcess(int xlop) /* Lose the postmaster's on-exit routines and port connections */ on_exit_reset(); + /* Release postmaster's working memory context */ + MemoryContextSwitchTo(TopMemoryContext); + MemoryContextDelete(PostmasterContext); + PostmasterContext = NULL; + BootstrapMain(ac, av); ExitPostmaster(0); } diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c index 470dcf11aa9..97e60a2fccf 100644 --- a/src/backend/storage/large_object/inv_api.c +++ b/src/backend/storage/large_object/inv_api.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/large_object/inv_api.c,v 1.103 2004/07/28 14:23:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/storage/large_object/inv_api.c,v 1.104 2004/07/31 00:45:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -54,14 +54,23 @@ open_lo_relation(void) /* Arrange for the top xact to own these relation references */ currentOwner = CurrentResourceOwner; - CurrentResourceOwner = TopTransactionResourceOwner; - - /* Use RowExclusiveLock since we might either read or write */ - if (lo_heap_r == NULL) - lo_heap_r = heap_openr(LargeObjectRelationName, RowExclusiveLock); - if (lo_index_r == NULL) - lo_index_r = index_openr(LargeObjectLOidPNIndex); + PG_TRY(); + { + CurrentResourceOwner = TopTransactionResourceOwner; + /* Use RowExclusiveLock since we might either read or write */ + if (lo_heap_r == NULL) + lo_heap_r = heap_openr(LargeObjectRelationName, RowExclusiveLock); + if (lo_index_r == NULL) + lo_index_r = index_openr(LargeObjectLOidPNIndex); + } + PG_CATCH(); + { + /* Ensure CurrentResourceOwner is restored on error */ + CurrentResourceOwner = currentOwner; + PG_RE_THROW(); + } + PG_END_TRY(); CurrentResourceOwner = currentOwner; } @@ -82,13 +91,22 @@ close_lo_relation(bool isCommit) ResourceOwner currentOwner; currentOwner = CurrentResourceOwner; - CurrentResourceOwner = TopTransactionResourceOwner; - - if (lo_index_r) - index_close(lo_index_r); - if (lo_heap_r) - heap_close(lo_heap_r, NoLock); + PG_TRY(); + { + CurrentResourceOwner = TopTransactionResourceOwner; + if (lo_index_r) + index_close(lo_index_r); + if (lo_heap_r) + heap_close(lo_heap_r, NoLock); + } + PG_CATCH(); + { + /* Ensure CurrentResourceOwner is restored on error */ + CurrentResourceOwner = currentOwner; + PG_RE_THROW(); + } + PG_END_TRY(); CurrentResourceOwner = currentOwner; } lo_heap_r = NULL; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index a8a7e0197ae..89ee40a5321 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.426 2004/07/28 22:05:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.427 2004/07/31 00:45:36 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -23,7 +23,6 @@ #include <signal.h> #include <fcntl.h> #include <sys/socket.h> -#include <errno.h> #if HAVE_SYS_SELECT_H #include <sys/select.h> #endif @@ -77,12 +76,6 @@ const char *debug_query_string; /* for pgmonitor and /* Note: whereToSendOutput is initialized for the bootstrap/standalone case */ CommandDest whereToSendOutput = Debug; -/* note: these declarations had better match tcopprot.h */ -sigjmp_buf Warn_restart; - -bool Warn_restart_ready = false; -bool InError = false; - /* flag for logging end of session */ bool Log_disconnections = false; @@ -1876,7 +1869,7 @@ quickdie(SIGNAL_ARGS) /* * Ideally this should be ereport(FATAL), but then we'd not get - * control back (perhaps could fix by doing local sigsetjmp?) + * control back... */ ereport(WARNING, (errcode(ERRCODE_CRASH_SHUTDOWN), @@ -1962,10 +1955,9 @@ StatementCancelHandler(SIGNAL_ARGS) int save_errno = errno; /* - * Don't joggle the elbow of proc_exit, nor an already-in-progress - * abort + * Don't joggle the elbow of proc_exit */ - if (!proc_exit_inprogress && !InError) + if (!proc_exit_inprogress) { InterruptPending = true; QueryCancelPending = true; @@ -2148,7 +2140,6 @@ usage(const char *progname) } - /* ---------------------------------------------------------------- * PostgresMain * postgres main loop -- all backends, interactive or otherwise start here @@ -2175,6 +2166,7 @@ PostgresMain(int argc, char *argv[], const char *username) int firstchar; char stack_base; StringInfoData input_message; + sigjmp_buf local_sigjmp_buf; volatile bool send_rfq = true; /* @@ -2772,50 +2764,61 @@ PostgresMain(int argc, char *argv[], const char *username) * * If an exception is encountered, processing resumes here so we abort * the current transaction and start a new one. + * + * You might wonder why this isn't coded as an infinite loop around + * a PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception + * handler in force at all during the CATCH part. By leaving the + * outermost setjmp always active, we have at least some chance of + * recovering from an error during error recovery. (If we get into + * an infinite loop thereby, it will soon be stopped by overflow of + * elog.c's internal state stack.) */ - if (sigsetjmp(Warn_restart, 1) != 0) + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { /* * NOTE: if you are tempted to add more code in this if-block, - * consider the probability that it should be in - * AbortTransaction() instead. - * - * Make sure we're not interrupted while cleaning up. Also forget - * any pending QueryCancel request, since we're aborting anyway. - * Force InterruptHoldoffCount to a known state in case we - * ereport'd from inside a holdoff section. + * consider the high probability that it should be in + * AbortTransaction() instead. The only stuff done directly here + * should be stuff that is guaranteed to apply *only* for outer-level + * error recovery, such as adjusting the FE/BE protocol status. + */ + + /* Since not using PG_TRY, must reset error stack by hand */ + error_context_stack = NULL; + + /* Prevent interrupts while cleaning up */ + HOLD_INTERRUPTS(); + + /* + * Forget any pending QueryCancel request, since we're returning + * to the idle loop anyway, and cancel the statement timer if running. */ - ImmediateInterruptOK = false; QueryCancelPending = false; - InterruptHoldoffCount = 1; - CritSectionCount = 0; /* should be unnecessary, but... */ disable_sig_alarm(true); QueryCancelPending = false; /* again in case timeout occurred */ + + /* + * Turn off these interrupts too. This is only needed here and not + * in other exception-catching places since these interrupts are + * only enabled while we wait for client input. + */ DisableNotifyInterrupt(); DisableCatchupInterrupt(); - debug_query_string = NULL; + + /* Report the error to the client and/or server log */ + EmitErrorReport(); /* - * If there's an active portal, mark it as failed + * Make sure debug_query_string gets reset before we possibly clobber + * the storage it points at. */ - if (ActivePortal) - ActivePortal->status = PORTAL_FAILED; + debug_query_string = NULL; /* - * Make sure we are in a valid memory context during recovery. - * - * We use ErrorContext in hopes that it will have some free space - * even if we're otherwise up against it... + * Abort the current transaction in order to recover. */ - MemoryContextSwitchTo(ErrorContext); - - /* Make sure we are using a sane ResourceOwner, too */ - CurrentResourceOwner = CurTransactionResourceOwner; - - /* Do the recovery */ - ereport(DEBUG2, - (errmsg_internal("AbortCurrentTransaction"))); AbortCurrentTransaction(); /* @@ -2823,24 +2826,10 @@ PostgresMain(int argc, char *argv[], const char *username) * for next time. */ MemoryContextSwitchTo(TopMemoryContext); - MemoryContextResetAndDeleteChildren(ErrorContext); - ActivePortal = NULL; - PortalContext = NULL; + FlushErrorState(); QueryContext = NULL; /* - * Clear flag to indicate that we got out of error recovery mode - * successfully. (Flag was set in elog.c before longjmp().) - */ - InError = false; - xact_started = false; - - /* - * Clear flag that causes accounting for cost based vacuum. - */ - VacuumCostActive = false; - - /* * If we were handling an extended-query-protocol message, * initiate skip till next Sync. This also causes us not to issue * ReadyForQuery (until we get Sync). @@ -2848,13 +2837,15 @@ PostgresMain(int argc, char *argv[], const char *username) if (doing_extended_query_message) ignore_till_sync = true; - /* - * Exit interrupt holdoff section we implicitly established above. - */ + /* We don't have a transaction command open anymore */ + xact_started = false; + + /* Now we can allow interrupts again */ RESUME_INTERRUPTS(); } - Warn_restart_ready = true; /* we can now handle ereport(ERROR) */ + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; PG_SETMASK(&UnBlockSig); diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 49e2a4b0082..8973cca6d26 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.81 2004/07/17 03:29:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.82 2004/07/31 00:45:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -246,94 +246,110 @@ PortalStart(Portal portal, ParamListInfo params) AssertState(portal->status == PORTAL_NEW); /* else extra PortalStart */ /* - * Set global portal context pointers. (Should we set QueryContext?) + * Set up global portal context pointers. (Should we set QueryContext?) */ saveActivePortal = ActivePortal; - ActivePortal = portal; saveResourceOwner = CurrentResourceOwner; - CurrentResourceOwner = portal->resowner; savePortalContext = PortalContext; - PortalContext = PortalGetHeapMemory(portal); + PG_TRY(); + { + ActivePortal = portal; + CurrentResourceOwner = portal->resowner; + PortalContext = PortalGetHeapMemory(portal); - oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - /* Must remember portal param list, if any */ - portal->portalParams = params; + /* Must remember portal param list, if any */ + portal->portalParams = params; - /* - * Determine the portal execution strategy - */ - portal->strategy = ChoosePortalStrategy(portal->parseTrees); + /* + * Determine the portal execution strategy + */ + portal->strategy = ChoosePortalStrategy(portal->parseTrees); - /* - * Fire her up according to the strategy - */ - switch (portal->strategy) - { - case PORTAL_ONE_SELECT: + /* + * Fire her up according to the strategy + */ + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: - /* - * Must set query snapshot before starting executor. - */ - SetQuerySnapshot(); + /* + * Must set query snapshot before starting executor. + */ + SetQuerySnapshot(); - /* - * Create QueryDesc in portal's context; for the moment, set - * the destination to None. - */ - queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees), - (Plan *) linitial(portal->planTrees), - None_Receiver, - params, - false); + /* + * Create QueryDesc in portal's context; for the moment, set + * the destination to None. + */ + queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees), + (Plan *) linitial(portal->planTrees), + None_Receiver, + params, + false); - /* - * Call ExecStart to prepare the plan for execution - */ - ExecutorStart(queryDesc, false, false); + /* + * Call ExecStart to prepare the plan for execution + */ + ExecutorStart(queryDesc, false, false); - /* - * This tells PortalCleanup to shut down the executor - */ - portal->queryDesc = queryDesc; + /* + * This tells PortalCleanup to shut down the executor + */ + portal->queryDesc = queryDesc; - /* - * Remember tuple descriptor (computed by ExecutorStart) - */ - portal->tupDesc = queryDesc->tupDesc; + /* + * Remember tuple descriptor (computed by ExecutorStart) + */ + portal->tupDesc = queryDesc->tupDesc; - /* - * Reset cursor position data to "start of query" - */ - portal->atStart = true; - portal->atEnd = false; /* allow fetches */ - portal->portalPos = 0; - portal->posOverflow = false; - break; + /* + * Reset cursor position data to "start of query" + */ + portal->atStart = true; + portal->atEnd = false; /* allow fetches */ + portal->portalPos = 0; + portal->posOverflow = false; + break; - case PORTAL_UTIL_SELECT: + case PORTAL_UTIL_SELECT: - /* - * We don't set query snapshot here, because PortalRunUtility - * will take care of it. - */ - portal->tupDesc = - UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt); + /* + * We don't set query snapshot here, because PortalRunUtility + * will take care of it. + */ + portal->tupDesc = + UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt); - /* - * Reset cursor position data to "start of query" - */ - portal->atStart = true; - portal->atEnd = false; /* allow fetches */ - portal->portalPos = 0; - portal->posOverflow = false; - break; + /* + * Reset cursor position data to "start of query" + */ + portal->atStart = true; + portal->atEnd = false; /* allow fetches */ + portal->portalPos = 0; + portal->posOverflow = false; + break; - case PORTAL_MULTI_QUERY: - /* Need do nothing now */ - portal->tupDesc = NULL; - break; + case PORTAL_MULTI_QUERY: + /* Need do nothing now */ + portal->tupDesc = NULL; + break; + } } + PG_CATCH(); + { + /* Uncaught error while executing portal: mark it dead */ + portal->status = PORTAL_FAILED; + + /* Restore global vars and propagate error */ + ActivePortal = saveActivePortal; + CurrentResourceOwner = saveResourceOwner; + PortalContext = savePortalContext; + + PG_RE_THROW(); + } + PG_END_TRY(); MemoryContextSwitchTo(oldContext); @@ -449,91 +465,108 @@ PortalRun(Portal portal, long count, portal->status = PORTAL_ACTIVE; /* - * Set global portal context pointers. + * Set up global portal context pointers. */ saveActivePortal = ActivePortal; - ActivePortal = portal; saveResourceOwner = CurrentResourceOwner; - CurrentResourceOwner = portal->resowner; savePortalContext = PortalContext; - PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; - QueryContext = portal->queryContext; + PG_TRY(); + { + ActivePortal = portal; + CurrentResourceOwner = portal->resowner; + PortalContext = PortalGetHeapMemory(portal); + QueryContext = portal->queryContext; - oldContext = MemoryContextSwitchTo(PortalContext); + oldContext = MemoryContextSwitchTo(PortalContext); - switch (portal->strategy) - { - case PORTAL_ONE_SELECT: - (void) PortalRunSelect(portal, true, count, dest); - /* we know the query is supposed to set the tag */ - if (completionTag && portal->commandTag) - strcpy(completionTag, portal->commandTag); + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: + (void) PortalRunSelect(portal, true, count, dest); + /* we know the query is supposed to set the tag */ + if (completionTag && portal->commandTag) + strcpy(completionTag, portal->commandTag); - /* Mark portal not active */ - portal->status = PORTAL_READY; + /* Mark portal not active */ + portal->status = PORTAL_READY; - /* - * Since it's a forward fetch, say DONE iff atEnd is now true. - */ - result = portal->atEnd; - break; + /* + * Since it's a forward fetch, say DONE iff atEnd is now true. + */ + result = portal->atEnd; + break; - case PORTAL_UTIL_SELECT: + case PORTAL_UTIL_SELECT: - /* - * If we have not yet run the utility statement, do so, - * storing its results in the portal's tuplestore. - */ - if (!portal->portalUtilReady) - { - DestReceiver *treceiver; - - PortalCreateHoldStore(portal); - treceiver = CreateDestReceiver(Tuplestore, portal); - PortalRunUtility(portal, linitial(portal->parseTrees), - treceiver, NULL); - (*treceiver->rDestroy) (treceiver); - portal->portalUtilReady = true; - } + /* + * If we have not yet run the utility statement, do so, + * storing its results in the portal's tuplestore. + */ + if (!portal->portalUtilReady) + { + DestReceiver *treceiver; + + PortalCreateHoldStore(portal); + treceiver = CreateDestReceiver(Tuplestore, portal); + PortalRunUtility(portal, linitial(portal->parseTrees), + treceiver, NULL); + (*treceiver->rDestroy) (treceiver); + portal->portalUtilReady = true; + } - /* - * Now fetch desired portion of results. - */ - (void) PortalRunSelect(portal, true, count, dest); + /* + * Now fetch desired portion of results. + */ + (void) PortalRunSelect(portal, true, count, dest); - /* - * We know the query is supposed to set the tag; we assume - * only the default tag is needed. - */ - if (completionTag && portal->commandTag) - strcpy(completionTag, portal->commandTag); + /* + * We know the query is supposed to set the tag; we assume + * only the default tag is needed. + */ + if (completionTag && portal->commandTag) + strcpy(completionTag, portal->commandTag); - /* Mark portal not active */ - portal->status = PORTAL_READY; + /* Mark portal not active */ + portal->status = PORTAL_READY; - /* - * Since it's a forward fetch, say DONE iff atEnd is now true. - */ - result = portal->atEnd; - break; + /* + * Since it's a forward fetch, say DONE iff atEnd is now true. + */ + result = portal->atEnd; + break; - case PORTAL_MULTI_QUERY: - PortalRunMulti(portal, dest, altdest, completionTag); + case PORTAL_MULTI_QUERY: + PortalRunMulti(portal, dest, altdest, completionTag); - /* Prevent portal's commands from being re-executed */ - portal->status = PORTAL_DONE; + /* Prevent portal's commands from being re-executed */ + portal->status = PORTAL_DONE; - /* Always complete at end of RunMulti */ - result = true; - break; + /* Always complete at end of RunMulti */ + result = true; + break; - default: - elog(ERROR, "unrecognized portal strategy: %d", - (int) portal->strategy); - result = false; /* keep compiler quiet */ - break; + default: + elog(ERROR, "unrecognized portal strategy: %d", + (int) portal->strategy); + result = false; /* keep compiler quiet */ + break; + } + } + PG_CATCH(); + { + /* Uncaught error while executing portal: mark it dead */ + portal->status = PORTAL_FAILED; + + /* Restore global vars and propagate error */ + ActivePortal = saveActivePortal; + CurrentResourceOwner = saveResourceOwner; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; + + PG_RE_THROW(); } + PG_END_TRY(); MemoryContextSwitchTo(oldContext); @@ -970,30 +1003,47 @@ PortalRunFetch(Portal portal, portal->status = PORTAL_ACTIVE; /* - * Set global portal context pointers. + * Set up global portal context pointers. */ saveActivePortal = ActivePortal; - ActivePortal = portal; saveResourceOwner = CurrentResourceOwner; - CurrentResourceOwner = portal->resowner; savePortalContext = PortalContext; - PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; - QueryContext = portal->queryContext; + PG_TRY(); + { + ActivePortal = portal; + CurrentResourceOwner = portal->resowner; + PortalContext = PortalGetHeapMemory(portal); + QueryContext = portal->queryContext; - oldContext = MemoryContextSwitchTo(PortalContext); + oldContext = MemoryContextSwitchTo(PortalContext); - switch (portal->strategy) + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: + result = DoPortalRunFetch(portal, fdirection, count, dest); + break; + + default: + elog(ERROR, "unsupported portal strategy"); + result = 0; /* keep compiler quiet */ + break; + } + } + PG_CATCH(); { - case PORTAL_ONE_SELECT: - result = DoPortalRunFetch(portal, fdirection, count, dest); - break; + /* Uncaught error while executing portal: mark it dead */ + portal->status = PORTAL_FAILED; - default: - elog(ERROR, "unsupported portal strategy"); - result = 0; /* keep compiler quiet */ - break; + /* Restore global vars and propagate error */ + ActivePortal = saveActivePortal; + CurrentResourceOwner = saveResourceOwner; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; + + PG_RE_THROW(); } + PG_END_TRY(); MemoryContextSwitchTo(oldContext); 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 diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README index 27180f6aff7..229a80a5a1e 100644 --- a/src/backend/utils/resowner/README +++ b/src/backend/utils/resowner/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.1 2004/07/17 03:30:10 tgl Exp $ +$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.2 2004/07/31 00:45:40 tgl Exp $ Notes about resource owners --------------------------- @@ -72,3 +72,7 @@ CurrentResourceOwner must point to the same resource owner that was current when the buffer, lock, or cache reference was acquired. It would be possible to relax this restriction given additional bookkeeping effort, but at present there seems no need. + +Code that transiently changes CurrentResourceOwner should generally use a +PG_TRY construct to ensure that the previous value of CurrentResourceOwner +is restored if control is lost during an error exit. diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index e2eb1183ef4..e3835971d91 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.1 2004/07/17 03:30:10 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.2 2004/07/31 00:45:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -99,6 +99,13 @@ typedef struct ResourceReleaseCallbackItem static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL; +/* Internal routines */ +static void ResourceOwnerReleaseInternal(ResourceOwner owner, + ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel); + + /***************************************************************************** * EXPORTED ROUTINES * *****************************************************************************/ @@ -162,17 +169,41 @@ ResourceOwnerRelease(ResourceOwner owner, bool isCommit, bool isTopLevel) { + /* Rather than PG_TRY at every level of recursion, set it up once */ + ResourceOwner save; + + save = CurrentResourceOwner; + PG_TRY(); + { + ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel); + } + PG_CATCH(); + { + CurrentResourceOwner = save; + PG_RE_THROW(); + } + PG_END_TRY(); + CurrentResourceOwner = save; +} + +static void +ResourceOwnerReleaseInternal(ResourceOwner owner, + ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel) +{ ResourceOwner child; ResourceOwner save; ResourceReleaseCallbackItem *item; /* Recurse to handle descendants */ for (child = owner->firstchild; child != NULL; child = child->nextchild) - ResourceOwnerRelease(child, phase, isCommit, isTopLevel); + ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel); /* * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc - * don't get confused. + * don't get confused. We needn't PG_TRY here because the outermost + * level will fix it on error abort. */ save = CurrentResourceOwner; CurrentResourceOwner = owner; |