PGEventProcs must not be allowed to break libpq

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: PGEventProcs must not be allowed to break libpq
Date: 2022-02-15 21:21:23
Message-ID: 3185105.1644960083@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

I've been fooling around with the duplicated-error-text issue
discussed in [1], and I think I have a solution that is fairly
bulletproof ... except that it cannot cope with this little gem
at the bottom of PQgetResult:

if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt,
res->events[i].passThrough))
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"),
res->events[i].name);
pqSetResultError(res, &conn->errorMessage);
res->resultStatus = PGRES_FATAL_ERROR;
break;
}
res->events[i].resultInitialized = true;

Deciding to rearrange the error situation at this point doesn't
work well for what I have in mind, mainly because we'd have already
done the bookkeeping that determines which error text to emit.
But more generally, it seems to me that allowing a failing PGEventProc
to cause this to happen is just not sane. It breaks absolutely
every guarantee you might think we have about how libpq will behave.
As an example that seems very plausible currently, if an event proc
doesn't know what a PGRES_PIPELINE_SYNC result is and fails on it,
will the application see behavior that's even a little bit sane?
I don't think so --- it will think the error results are server
failures, and then be very confused when answers arrive anyway.

Just to add insult to injury, the "break" makes for some pretty
inconsistent semantics for other eventprocs that may be registered.
Not to mention that previously-called eventprocs might be very
surprised to find that a non-error PGresult has turned into an
error one underneath them.

I think the behavior for failing event procs ought to be that they're
just ignored henceforth, so we'd replace this bit with

if (res->events[i].proc(PGEVT_RESULTCREATE, &evt,
res->events[i].passThrough))
res->events[i].resultInitialized = true;

and continue the policy that not-resultInitialized events don't get
PGEVT_RESULTDESTROY or PGEVT_RESULTCOPY calls. (This'd also allow
the behavior of PQfireResultCreateEvents to be more closely synced
with PQgetResult.)

Likewise, I do not think it's acceptable to let PGEVT_RESULTCOPY
callbacks break PQcopyResult (just in our own code, that breaks
single-row mode). So I'd drop the forced PQclear there, and say the
only consequence is to not set resultInitialized so that that event
won't get PGEVT_RESULTDESTROY later.

I also find the existing behavior that failing PGEVT_CONNRESET
events break the connection to be less than sane, and would propose
ignoring the function result in those calls too. It's less critical
to my immediate problem though.

Thoughts?

regards, tom lane

[1] https://www.postgresql.org/message-id/flat/ab4288f8-be5c-57fb-2400-e3e857f53e46(at)enterprisedb(dot)com

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Tom Lane 2022-02-15 21:28:27 Re: last_archived_wal is not necessary the latest WAL file (was Re: pgsql: Add test case for an archive recovery corner case.)
Previous Message Heikki Linnakangas 2022-02-15 21:20:50 last_archived_wal is not necessary the latest WAL file (was Re: pgsql: Add test case for an archive recovery corner case.)