Re: Error-safe user functions - Mailing list pgsql-hackers

From Andres Freund
Subject Re: Error-safe user functions
Date
Msg-id 20221207233518.sudtaxn6omtvpzvr@awork3.anarazel.de
Whole thread Raw
In response to Re: Error-safe user functions  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Error-safe user functions
Re: Error-safe user functions
List 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



pgsql-hackers by date:

Previous
From: Andrey Chudnovsky
Date:
Subject: Re: [PoC] Federated Authn/z with OAUTHBEARER
Next
From: Andres Freund
Date:
Subject: Re: [PATCH] pg_dump: lock tables in batches