Thread: pg_croak, or something like it?

pg_croak, or something like it?

From
Robert Haas
Date:
Hi,

Recent developments on the "backup manifest" thread and others have
caused me to take an interest in making more code that has
historically been backend-only accessible in frontend environments
also. I'm pretty sure I'm not alone in having often wished for more
backend-only facilities to be available in front-end code, because
many of them are very convenient, but up until now I haven't been
motivated enough to do much about it.

Probably the thorniest problem is the backend's widespread dependence
on ereport() and elog(). Now, it would not be enormously difficult to
code up something that will sigsetjmp() and siglongjmp() in front-end
code just as we do in backend code, but I think it would be largely
missing the point. Just jumping out of a function some place and back
to the top level is not very safe, because you will tend to leak
resources and leave data structures in broken states. In the backend,
we've made this safe via transaction cleanup, which arranges to
release resources, including memory, at the proper times. Getting this
to work everywhere and for all the kinds of resources that matter has
been a pretty significant undertaking; while frontend environments
tend to be simpler, I think it's likely to take a good deal of work
and thought to figure it all out. There's another problem, too: when
ereport() is used in the backend, it reports not only an error message
but a bunch of other things like an error code, an optional error
detail, and an error context. It might be good to introduce some of
these concepts on the frontend side, but it will be confusing if
frontend errors start getting reported just like backend errors, so
here again I feel we need to think carefully.

That being said, not all uses of ereport() and elog() are created
equal. Sometimes, they are just used to report warnings, which
typically don't contain much more than a primary message, and
sometimes they are used to report can't-happen conditions, which ERROR
in backend code and could probably just print a message and exit() in
frontend code. Providing a general way to do this kind of thing seems
a lot easier than solving the whole problem, and it would allow us to
avoid continuing to copy stuff like this:

#ifndef FRONTEND
#define pg_log_warning(...) elog(WARNING, __VA_ARGS__)
#else
#include "common/logging.h"
#endif

We have two copies of that already, and I don't think we should
continue to add more. So, what I'd like to propose is a pair of
macros, one of which arranges for a warning of an appropriate sort for
either a backend or frontend environment, and the other of which is
used for a can't happen condition that should either ERROR or just
exit. Taking a page from my Perl programming background, I propose to
call these pg_carp() and pg_croak(), although I'm not in love with
those names so let the bikeshedding commence. Something like:

#ifdef FRONTEND
#define pg_croak(...) do { pg_log_fatal(__VA_ARGS__); exit(1) } while (0)
#define pg_carp(...) pg_log_warning(__VA_ARGS__);
#else
#define pg_croak(...) elog(ERROR, __VA_ARGS__)
#define pg_carp(...) elog(WARNING, __VA_ARGS__)
#endif

It is of course somewhat questionable to consider a "croak" as
effectively *fatal* on the frontend side but only ERROR rather than
FATAL on the backend side, but for the kinds of things I'm looking at
it seems like the most useful definition. If the JSON parser goes
horribly wrong to due to some logic bug, it is neither useful nor
friendly to terminate the session, but a frontend tool is probably
fine for it to just exit. The user perception will be, in each case,
that the last command they attempted (either from the command-line or
from psql, as the case may be) failed but that they are free to enter
more commands without needing to start a new session (either of psql
or bash or whatever).

Thoughts?

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: pg_croak, or something like it?

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> Probably the thorniest problem is the backend's widespread dependence
> on ereport() and elog(). Now, it would not be enormously difficult to
> code up something that will sigsetjmp() and siglongjmp() in front-end
> code just as we do in backend code, but I think it would be largely
> missing the point.

Agreed that we don't want to introduce anything like transaction
abort recovery in our frontend tools.

> That being said, not all uses of ereport() and elog() are created
> equal. Sometimes, they are just used to report warnings, which
> typically don't contain much more than a primary message, and
> sometimes they are used to report can't-happen conditions, which ERROR
> in backend code and could probably just print a message and exit() in
> frontend code. Providing a general way to do this kind of thing seems
> a lot easier than solving the whole problem,

I think the $64 question is whether handling those two cases is sufficient
for *all* elog/ereport usages in the code we'd like to move to src/common.
If it is, great; but if it isn't, we still have a problem to solve, and
it's not clear that solving that problem won't yield a better answer for
these cases too.

> Taking a page from my Perl programming background, I propose to
> call these pg_carp() and pg_croak(), although I'm not in love with
> those names so let the bikeshedding commence.

Yeah, I'm not in love with those names either, partly because I think
they carry some baggage about when to use them and what the reports
are likely to look like.  But sans caffeine, I don't have a better idea
either.

> Something like:
> #ifdef FRONTEND
> #define pg_croak(...) do { pg_log_fatal(__VA_ARGS__); exit(1) } while (0)
> #define pg_carp(...) pg_log_warning(__VA_ARGS__);
> #else
> #define pg_croak(...) elog(ERROR, __VA_ARGS__)
> #define pg_carp(...) elog(WARNING, __VA_ARGS__)
> #endif

Hm, the thing that jumps out at me about those is the lack of attention
to translatability.  Sure, for really "can't happen" cases we probably
just want to use bare elog with a non-translated message.  But warnings
are user-facing, and there will be an enormous temptation to use the
croak mechanism for user-facing errors too, and those should be
translated.  There's also a problem that there's noplace to add an
ERRCODE; that's flat out not acceptable for backend code that's
reporting anything but absolutely-cannot-happen cases.

> It is of course somewhat questionable to consider a "croak" as
> effectively *fatal* on the frontend side but only ERROR rather than
> FATAL on the backend side, but for the kinds of things I'm looking at
> it seems like the most useful definition.

Agreed there.

What I keep thinking is that we should stick with ereport() as the
reporting notation, and invent a frontend-side implementation of it
that covers the cases you mention (ie WARNING and ERROR ... and maybe
DEBUG?), ignoring any components of the ereport that aren't helpful for
the purpose.  That'd eliminate the temptation to shave the quality of
the backend-side error reports, and we still end up with about the same
basic functionality on frontend side.

            regards, tom lane



Re: pg_croak, or something like it?

From
Robert Haas
Date:
On Mon, Jan 27, 2020 at 10:08 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Something like:
> > #ifdef FRONTEND
> > #define pg_croak(...) do { pg_log_fatal(__VA_ARGS__); exit(1) } while (0)
> > #define pg_carp(...) pg_log_warning(__VA_ARGS__);
> > #else
> > #define pg_croak(...) elog(ERROR, __VA_ARGS__)
> > #define pg_carp(...) elog(WARNING, __VA_ARGS__)
> > #endif
>
> Hm, the thing that jumps out at me about those is the lack of attention
> to translatability.  Sure, for really "can't happen" cases we probably
> just want to use bare elog with a non-translated message.  But warnings
> are user-facing, and there will be an enormous temptation to use the
> croak mechanism for user-facing errors too, and those should be
> translated.  There's also a problem that there's noplace to add an
> ERRCODE; that's flat out not acceptable for backend code that's
> reporting anything but absolutely-cannot-happen cases.

I sorta meant to mention the translatability issues, but it went out
of my head; not enough caffeine here either, I guess.
pg_log_generic_v() does fmt = _(fmt) and
FRONTEND_COMMON_GETTEXT_TRIGGERS includes pg_log_error and friends, so
I guess the idea is that everything that goes through these routines
gets transalated. But then how does one justify this code that I
quoted before?

#ifndef FRONTEND
#define pg_log_warning(...) elog(WARNING, __VA_ARGS__)
#else
#include "common/logging.h"
#endif

These messages are, I guess, user-facing on the frontend, but can't
happen on the backend? Uggh.

> What I keep thinking is that we should stick with ereport() as the
> reporting notation, and invent a frontend-side implementation of it
> that covers the cases you mention (ie WARNING and ERROR ... and maybe
> DEBUG?), ignoring any components of the ereport that aren't helpful for
> the purpose.  That'd eliminate the temptation to shave the quality of
> the backend-side error reports, and we still end up with about the same
> basic functionality on frontend side.

Well, the cases that I'm concerned about use elog(), not ereport(), so
I don't think this would help me very much. They actually are
can't-happen messages. If I were to do what you propose here, I'd then
have to run around and convert a bunch of elog() calls to ereport() to
make it work. That seems like it's going in the direction of
increasing complexity rather than reducing it, and I don't much care
for the idea that we should run around changing things like:

elog(ERROR, "unexpected json parse state: %d", ctx);

to say:

ereport(ERROR, (errmsg_internal("unexpected json parse state: %d", ctx)));

That's not a step forward in my book, especially because it'll be
*necessary* for code in src/common and optional in other places. Also,
using ereport() -- or elog() -- in frontend code seems like a large
step down the slippery slope of leading people to believe that error
recovery in the frontend can, does, or should work like error recovery
in the backend, and I think if we do even the slightest thing to feed
that impression we will regret it bitterly.

That being said, I do agree that there's a danger of people thinking
that they can use my proposed pg_croak() for user-facing messages.
Now, comments would help. (I note in passing that the comments in
common/logging.h make no mention of translatability, which IMHO they
probably should.) But we could also try to make it clear via the names
themselves, like call the macros cant_happen_error() and
cant_happen_warning(). I actually thought about that option at one
point while I was fiddling around with this, but I am psychologically
incapable of coping with spelling "can't" without an apostrophe.
However, maybe some other spelling would dodge that problem.
pg_cannot_happen_error() and pg_cannot_happen_warning()? I don't know.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: pg_croak, or something like it?

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Jan 27, 2020 at 10:08 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> What I keep thinking is that we should stick with ereport() as the
>> reporting notation, and invent a frontend-side implementation of it
>> that covers the cases you mention (ie WARNING and ERROR ... and maybe
>> DEBUG?), ignoring any components of the ereport that aren't helpful for
>> the purpose.  That'd eliminate the temptation to shave the quality of
>> the backend-side error reports, and we still end up with about the same
>> basic functionality on frontend side.

> Well, the cases that I'm concerned about use elog(), not ereport(), so
> I don't think this would help me very much.

So?  elog() is just a specific degenerate case of ereport().  If we have
a way to implement ereport() on frontend side then we can surely do
elog() too.

What it sounds to me like you want to do is implement (some equivalent of)
elog() but not ereport() for this environment.  I'm going to resist that
pretty strongly, because I think it will lead directly to abuse of elog()
for user-facing errors, with a consequent degradation of the user
experience when that code executes on backend side.  I do not believe
that there are no user-facing error cases in the JSON parser, for
example; much less that we'll never introduce any in future.

> That's not a step forward in my book, especially because it'll be
> *necessary* for code in src/common and optional in other places. Also,
> using ereport() -- or elog() -- in frontend code seems like a large
> step down the slippery slope of leading people to believe that error
> recovery in the frontend can, does, or should work like error recovery
> in the backend, and I think if we do even the slightest thing to feed
> that impression we will regret it bitterly.

I'm not exactly buying that.  For subsystems like the JSON parser,
at least, either elog or ereport with ERROR just means "I'm throwing
up my hands, what happens next does not concern me".  I don't see
any problem with interpreting that as leading to exit(1) on frontend
side.  I'm also not seeing why using some other, randomly different
notation for what are fundamentally going to be the same semantics
would really improve the situation.

            regards, tom lane



Re: pg_croak, or something like it?

From
Robert Haas
Date:
On Mon, Jan 27, 2020 at 10:50 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> So?  elog() is just a specific degenerate case of ereport().  If we have
> a way to implement ereport() on frontend side then we can surely do
> elog() too.

I suppose that's true.

> What it sounds to me like you want to do is implement (some equivalent of)
> elog() but not ereport() for this environment.  I'm going to resist that
> pretty strongly, because I think it will lead directly to abuse of elog()
> for user-facing errors, with a consequent degradation of the user
> experience when that code executes on backend side.  I do not believe
> that there are no user-facing error cases in the JSON parser, for
> example; much less that we'll never introduce any in future.

You clearly haven't read the thread on this topic, or at least not
very carefully.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: pg_croak, or something like it?

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Jan 27, 2020 at 10:50 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> What it sounds to me like you want to do is implement (some equivalent of)
>> elog() but not ereport() for this environment.  I'm going to resist that
>> pretty strongly, because I think it will lead directly to abuse of elog()
>> for user-facing errors, with a consequent degradation of the user
>> experience when that code executes on backend side.  I do not believe
>> that there are no user-facing error cases in the JSON parser, for
>> example; much less that we'll never introduce any in future.

> You clearly haven't read the thread on this topic, or at least not
> very carefully.

I have not, but I'm still going to stand by that point.  It is not
credible that the code we will want to share between frontend and
backend will never contain any user-facing error reports.  Designing
a reporting mechanism that assumes that is just going to lead to
degraded reporting of things that are indeed user-facing.

            regards, tom lane



Re: pg_croak, or something like it?

From
Robert Haas
Date:
On Mon, Jan 27, 2020 at 11:07 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I have not, but I'm still going to stand by that point.  It is not
> credible that the code we will want to share between frontend and
> backend will never contain any user-facing error reports.

It's hard to refute a statement this general; it can only devolve into
an argument about what we will want to do in the future. And, if I may
be permitted to appeal to a highier authority on that topic, I think
this quote is highly relevant: "Difficult to see. Always in motion is
the future."

What I was hoping to do in this thread was to focus on the problem of
which I have several instances immediately at hand, which is handling
can't-happen conditions, rather than blowing open the issue in its
full generality. I think there is a meaningful amount of code in the
backend where that is, or can be made to be, the only issue that needs
to be solved, and I therefore think that it is a reasonable special
case to tackle first. Of course, I can't force you to have that
conversation, but I don't think refusing to have it is going to make
the problem go away. Realistically, people aren't going to stop moving
code to src/common; what they're going to do is put special-case hacks
in each file, like we already have in a couple of places, instead of
using some more general solution upon which we might try to agree.

In other words, somebody who comes across a chunk of code that uses
ereport() extensively might conceivably give up on moving it to
src/common, but somebody who finds one elog(ERROR, "oops this is
broken") is not going to for that reason give up. They are going to do
something to get around it. Better for them all to do the same thing,
and something that's had some general thought given to it, than for
each person who runs into such a problem to hand-roll their own way of
handling it.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company