Re: Error-safe user functions

From: Andres Freund <andres(at)anarazel(dot)de>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: Andrew Dunstan <andrew(at)dunslane(dot)net>, "David G(dot) Johnston" <david(dot)g(dot)johnston(at)gmail(dot)com>, Corey Huinker <corey(dot)huinker(at)gmail(dot)com>, Alvaro Herrera <alvherre(at)alvh(dot)no-ip(dot)org>, Joe Conway <mail(at)joeconway(dot)com>, Robert Haas <robertmhaas(at)gmail(dot)com>, Nikita Glukhov <n(dot)gluhov(at)postgrespro(dot)ru>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: Error-safe user functions
Date: 2022-12-07 23:35:18
Message-ID: 20221207233518.sudtaxn6omtvpzvr@awork3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On 2022-12-07 17:32:21 -0500, Tom Lane wrote:
> I already pushed the 0000 elog-refactoring patch, since that seemed
> uncontroversial. 0001 attached covers the same territory as before,
> but I regrouped the rest so that 0002 installs the new test support
> functions, then 0003 adds both the per-datatype changes and
> corresponding test cases for bool, int4, arrays, and records.
> The idea here is that 0003 can be pointed to as a sample of what
> has to be done to datatype input functions, while the preceding
> patches can be cited as relevant documentation. (I've not decided
> whether to squash 0001 and 0002 together or commit them separately.

I think they make sense as is.

> Does it make sense to break 0003 into 4 separate commits, or is
> that overkill?)

I think it'd be fine either way.

> + * If "context" is an ErrorSaveContext node, but the node creator only wants
> + * notification of the fact of a soft error without any details, just set
> + * the error_occurred flag in the ErrorSaveContext node and return false,
> + * which will cause us to skip the remaining error processing steps.
> + *
> + * Otherwise, create and initialize error stack entry and return true.
> + * Subsequently, errmsg() and perhaps other routines will be called to further
> + * populate the stack entry. Finally, errsave_finish() will be called to
> + * tidy up.
> + */
> +bool
> +errsave_start(NodePtr context, const char *domain)

I wonder if there are potential use-cases for levels other than ERROR. I can
potentially see us wanting to defer some FATALs, e.g. when they occur in
process exit hooks.

> +{
> + ErrorSaveContext *escontext;
> + ErrorData *edata;
> +
> + /*
> + * Do we have a context for soft error reporting? If not, just punt to
> + * errstart().
> + */
> + if (context == NULL || !IsA(context, ErrorSaveContext))
> + return errstart(ERROR, domain);
> +
> + /* Report that a soft error was detected */
> + escontext = (ErrorSaveContext *) context;
> + escontext->error_occurred = true;
> +
> + /* Nothing else to do if caller wants no further details */
> + if (!escontext->details_wanted)
> + return false;
> +
> + /*
> + * Okay, crank up a stack entry to store the info in.
> + */
> +
> + recursion_depth++;
> +
> + /* Initialize data for this error frame */
> + edata = get_error_stack_entry();

For a moment I was worried that it could lead to odd behaviour that we don't
do get_error_stack_entry() when !details_wanted, due to not raising an error
we'd otherwise raise. But that's a should-never-be-reached case, so ...

> +/*
> + * errsave_finish --- end a "soft" error-reporting cycle
> + *
> + * If errsave_start() decided this was a regular error, behave as
> + * errfinish(). Otherwise, package up the error details and save
> + * them in the ErrorSaveContext node.
> + */
> +void
> +errsave_finish(NodePtr context, const char *filename, int lineno,
> + const char *funcname)
> +{
> + ErrorSaveContext *escontext = (ErrorSaveContext *) context;
> + ErrorData *edata = &errordata[errordata_stack_depth];
> +
> + /* verify stack depth before accessing *edata */
> + CHECK_STACK_DEPTH();
> +
> + /*
> + * If errsave_start punted to errstart, then elevel will be ERROR or
> + * perhaps even PANIC. Punt likewise to errfinish.
> + */
> + if (edata->elevel >= ERROR)
> + {
> + errfinish(filename, lineno, funcname);
> + pg_unreachable();
> + }

It seems somewhat ugly transport this knowledge via edata->elevel, but it's
not too bad.

> +/*
> + * We cannot include nodes.h yet, so make a stub reference. (This is also
> + * used by fmgr.h, which doesn't want to depend on nodes.h either.)
> + */
> +typedef struct Node *NodePtr;

Seems like it'd be easier to just forward declare the struct, and use the
non-typedef'ed name in the header than to have to deal with these
interdependencies and the differing typenames.

> +/*----------
> + * Support for reporting "soft" errors that don't require a full transaction
> + * abort to clean up. This is to be used in this way:
> + * errsave(context,
> + * errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
> + * errmsg("invalid input syntax for type %s: \"%s\"",
> + * "boolean", in_str),
> + * ... other errxxx() fields as needed ...);
> + *
> + * "context" is a node pointer or NULL, and the remaining auxiliary calls
> + * provide the same error details as in ereport(). If context is not a
> + * pointer to an ErrorSaveContext node, then errsave(context, ...)
> + * behaves identically to ereport(ERROR, ...). If context is a pointer
> + * to an ErrorSaveContext node, then the information provided by the
> + * auxiliary calls is stored in the context node and control returns
> + * normally. The caller of errsave() must then do any required cleanup
> + * and return control back to its caller. That caller must check the
> + * ErrorSaveContext node to see whether an error occurred before
> + * it can trust the function's result to be meaningful.
> + *
> + * errsave_domain() allows a message domain to be specified; it is
> + * precisely analogous to ereport_domain().
> + *----------
> + */
> +#define errsave_domain(context, domain, ...) \
> + do { \
> + NodePtr context_ = (context); \
> + pg_prevent_errno_in_scope(); \
> + if (errsave_start(context_, domain)) \
> + __VA_ARGS__, errsave_finish(context_, __FILE__, __LINE__, __func__); \
> + } while(0)

Perhaps worth noting here that the reason why the errsave_start/errsave_finish
split exist differs a bit from the reason in ereport_domain()? "Over there"
it's just about not wanting to incur overhead when the message isn't logged,
but here we'll always have >= ERROR, but ->details_wanted can still lead to
not wanting to incur the overhead.

> /*
> diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
> index db843a0fbf..bdafcff02d 100644
> --- a/src/backend/utils/adt/rowtypes.c
> +++ b/src/backend/utils/adt/rowtypes.c
> @@ -77,6 +77,7 @@ record_in(PG_FUNCTION_ARGS)
> char *string = PG_GETARG_CSTRING(0);
> Oid tupType = PG_GETARG_OID(1);
> int32 tupTypmod = PG_GETARG_INT32(2);
> + Node *escontext = fcinfo->context;
> HeapTupleHeader result;
> TupleDesc tupdesc;
> HeapTuple tuple;
> @@ -100,7 +101,7 @@ record_in(PG_FUNCTION_ARGS)
> * supply a valid typmod, and then we can do something useful for RECORD.
> */
> if (tupType == RECORDOID && tupTypmod < 0)
> - ereport(ERROR,
> + ereturn(escontext, (Datum) 0,
> (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> errmsg("input of anonymous composite types is not implemented")));
>

Is it ok that we throw an error in lookup_rowtype_tupdesc()? Normally those
should not be reachable by users, I think? The new testing functions might
reach it, but that seems fine, they're test functions.

Greetings,

Andres Freund

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Andres Freund 2022-12-07 23:45:05 Re: [PATCH] pg_dump: lock tables in batches
Previous Message Andrey Chudnovsky 2022-12-07 23:22:51 Re: [PoC] Federated Authn/z with OAUTHBEARER