Re: BUG #15449: file_fdw using program cause exit code error when using LIMIT

From: Kyotaro HORIGUCHI <horiguchi(dot)kyotaro(at)lab(dot)ntt(dot)co(dot)jp>
To: fujita(dot)etsuro(at)lab(dot)ntt(dot)co(dot)jp
Cc: tgl(at)sss(dot)pgh(dot)pa(dot)us, eric(dot)cyr(at)gmail(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: BUG #15449: file_fdw using program cause exit code error when using LIMIT
Date: 2018-11-06 03:53:04
Message-ID: 20181106.125304.193575333.horiguchi.kyotaro@lab.ntt.co.jp
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs pgsql-hackers

At Fri, 02 Nov 2018 22:05:36 +0900, Etsuro Fujita <fujita(dot)etsuro(at)lab(dot)ntt(dot)co(dot)jp> wrote in <5BDC4BA0(dot)7050106(at)lab(dot)ntt(dot)co(dot)jp>
> (2018/10/29 15:58), Kyotaro HORIGUCHI wrote:
> > At Tue, 23 Oct 2018 13:21:31 +0100, Tom Lane<tgl(at)sss(dot)pgh(dot)pa(dot)us> wrote
> > in<18397(dot)1540297291(at)sss(dot)pgh(dot)pa(dot)us>
> >> After a bit of thought, the problem here is blindingly obvious:
> >> we generally run the backend with SIGPIPE handing set to SIG_IGN,
> >> and evidently popen() allows the called program to inherit that,
> >> at least on these platforms.
> >>
> >> So what we need to do is make sure the called program inherits SIG_DFL
> >> handling for SIGPIPE, and then special-case that result as not being
> >> a failure. The attached POC patch does that and successfully makes
> >> the file_fdw problem go away for me.
>
> Thanks for working on this!
>
> >> It's just a POC because there are some things that need more thought
> >> than I've given them:
> >>
> >> 1. Is it OK to revert SIGPIPE to default processing for *all* programs
> >> launched through OpenPipeStream? If not, what infrastructure do we
> >> need to add to control that? In particular, is it sane to revert
> >> SIGPIPE for a pipe program that we will write to not read from?
> >> (I thought probably yes, because that is the normal Unix behavior,
> >> but it could be debated.)
> >
> > Forcing to ignore SIGPIPE lets the program to handle EPIPE, which
> > might not be handled poperly since it would be unexpected by the
> > program.
> >
> > If we don't read from the pipe (that is, we are writing to it),
> > anyway we shouldn't change the behavior since SIGPIPE can come
> > from another pipe.
>
> I'm a bit confused here. Horiguchi-san, are you saying that the
> called program that we will read from should inherit SIG_DFL for
> SIGPIPE, as proposed in the POC patch, but the called program that we
> will write to should inherit SIG_IGN as it currently does?

Maybe yes. The understanding of the first paragram looks
right. But in my second paragraph, I said that we shouldn't set
SIGPIPE to other than SIG_DFL at exec() time even if we are to
read the pipe because it can be writing to another pipe and the
change can affect the program's behavior.

> ISTM that it would be OK to inherit SIG_DFL in both cases, because I
> think it would be the responsibility of the called program to handle
> SIGPIPE properly (if necessary) in both cases. Maybe I'm missing
> something, though.

So, I think we *should* (not just OK to) restore SIGPIPE to
SIG_DFL in any case here to prevent undetermined situation for
the program.

> >> 2. Likewise, is it always OK for ClosePipeToProgram to ignore a
> >> SIGPIPE failure? (For ordinary COPY, maybe it shouldn't, since
> >> we don't intend to terminate that early.)
> >
> > Since the SIGPIPE may come from another pipe, I think we
> > shouldn't generally.
>
> Agreed; if ClosePipeToProgram ignores that failure, we would fail to
> get a better error message in CopySendEndOfRow if the called program
> (inheriting SIG_DFL for SIGPIPE) was terminated on SIGPIPE:
>
> if (cstate->is_program)
> {
> if (errno == EPIPE)
> {
> /*
> * The pipe will be closed automatically on error at
> * the end of transaction, but we might get a better
> * error message from the subprocess' exit code than
> * just "Broken Pipe"
> */
> ClosePipeToProgram(cstate);
>
> /*
> * If ClosePipeToProgram() didn't throw an error, the
> * program terminated normally, but closed the pipe
> * first. Restore errno, and throw an error.
> */
> errno = EPIPE;
> }
> ereport(ERROR,
> (errcode_for_file_access(),
> errmsg("could not write to COPY program: %m")));
> }

Mmm, that's EPIPE of fwrite, not SIGNALLED&SIGPIPE of called
program's exit status. So it is irrelevant to called program's
SIGPIPE setup. It requires SIGPIPE to be kept to SIG_IGN on the
backend side.

> > But iff we are explicitly stop reading from
> > the pipe before detecting an error, it can be ignored since we
> > aren't interested in further failure.
>
> You mean that we should ignore other failures of the called program
> when we stop reading from the pipe early?

Yes, we have received sufficient data from the pipe then closed
it successfully. The program may want write more but we don't
want it. We ragher should ignore SIGPIPE exit code of the program
since closing our writing end of a pipe is likely to cause it and
even if it comes from another pipe, we can assume that the
SIGPIPE immediately stopped the program before returning any
garbage to us.

> > Thus ClosePipeToProgram
> > might need an extra parameter or CopyState may need an additional
> > flag named, say, "explicit_close" (or
> > "ignore_sigpipe_on_pclose"..) in CopyState which will be set in
> > EndCopy.
> >
> >> 3. Maybe this should be implemented at some higher level?
> >
> > I'm not sure, but it seems to me not needed.
>
> ISTM that it's a good idea to control the ClosePipeToProgram behavior
> by a new member for CopyState.
>
> >> 4. Are there any other signals we ought to be reverting to default
> >> behavior before launching a COPY TO/FROM PROGRAM?
> >
> > All handlers other than SIG_DEF and IGN are reset on exec().
> > Only SIGUSR2 seems to be set to SIG_IGN, but it's not likely to
> > harm anything. Perhaps it would be safer against future changes
> > if we explicitly reset all changed actions to SIG_DEF, but it
> > might be too-much..
>
> Not sure, but reverting SIGPIPE to default would be enough as a fix
> for the original issue, if we go with the POC patch.

Agreed. I wonder why there's no API that resets all handlers to
SIG_DFL at once or some flag telling to exec() that it should
start with default signal handler set.

I tried that but found that fread() doesn't detect termination of
called program by a signal as an error. So just set a flag in
EndCopyFrom() ends with concealing of the error.

Finally in the attached PoC, I set cstate->eof_reached on failure
of NextCopyFromRawFields then if eof_reached we don't ingore
SIGPIPE. For a program like
"puts("test1");fflush(stdout);kill(getpid(), SIGPIPE);", I had
the following behavior. (I got an error without the fflush() but
it is inevitable).

=# select * from ft2 limit 1;
a
-------
test1

=# select * from ft2 limit 2;
ERROR: program "/home/horiguti/work/exprog" failed
DETAIL: child process was terminated by signal 13: Broken pipe

For the original case:

=# select * from ft1 limit 1;
a
------
test
=# select * from ft1 limit 2;
a
------
test
(1 row)

I didn't confirmed whether it is complete.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachment Content-Type Size
process-sigpipe-normally-in-copy-from-program-kyota-1.patch text/x-patch 3.1 KB

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Amit Langote 2018-11-06 09:17:49 Re: BUG #15212: Default values in partition tables don't work as expected and allow NOT NULL violation
Previous Message Pavel Stehule 2018-11-06 03:44:09 Re: BUG #15487: undefined symbol: geod_polygon_init

Browse pgsql-hackers by date

  From Date Subject
Next Message Tatsuo Ishii 2018-11-06 04:02:25 Re: Code of Conduct plan,Re: Code of Conduct plan
Previous Message Alvaro Herrera 2018-11-06 03:52:49 Re: Tid scan improvements