fish-shell icon indicating copy to clipboard operation
fish-shell copied to clipboard

Builtins writing to stdout don't abort when stdout has closed

Open mqudsi opened this issue 2 years ago • 4 comments
trafficstars

Using head to terminate long-running command after x lines is a common idiom. Here, it's used to stop reading a terabyte log file after sufficient matches are found via grep:

> tac /hddpool/nginx.access.log | grep -i keep-alive | head -n8
<matches elided> # to stdout, from `grep`
/hddpool/nginx.access.log: Os { code: 32, kind: BrokenPipe, message: "Broken pipe" } # to stderr, from `tac`

but with string match instead of grep:

> tac /hddpool/nginx.access.log | string match -ei keep-alive | head -n8
<matches elided> # to stdout, from `grep`
# script hangs here w/ `tac` running but `head` aborted (currently a zombie)

When stdout is closed, builtins need to terminate and then exit (closing their stdin in the process).

I already patched all the builtin write functions to return success/failure in 3.6.0, but we're going to need to start checking the return code in each builtin as it writes to stdout so we can abort early.

mqudsi avatar Jan 19 '23 20:01 mqudsi

For string match specifically, variable importing makes this more complex.

We do keep the first matched capturing group (without --all), but if we don't yet have one it seems correct to keep on trying. With --all we might need to exhaust stdin anyway.

faho avatar Jan 19 '23 20:01 faho

Looking more at this, I think the only builtins that really need this are "string" and "path" and maybe "complete --do-complete" - the rest doesn't really produce enough output to be worth it.

Even there, some paths need to exhaust the input first ("path sort").

One decision I would like here is to not return failure just because stdout was closed - if e.g. path filter has printed one thing and would return success if that was the last, it's awkward to have it fail just because it can't write more.

faho avatar May 08 '24 09:05 faho

I agree — if I recall correctly, in my previous testing, an abruptly closed stdout fd generally does not result in a change to the exit code of core Unix utilities. (This would only affect $pipe_status, in all cases.)

mqudsi avatar May 09 '24 15:05 mqudsi

an abruptly closed stdout fd generally does not result in a change to the exit code of core Unix utilities

No, it usually does change the exit code.

See e.g. seq 1 100000 | head -n 2, where seq exits with a status of 141 - which is of course 128 + 13, because it got a SIGPIPE.

If the command doesn't arrange for anything else, it will be killed by SIGPIPE, so that's the normal case.

I'm saying that it's typically not helpful, and is a major annoyance with the "bash strict mode" concept (of errexit + pipefail plus some other bits), where it will kill the entire script depending on whether the first command's output fit into the last write (do seq 1 100 | head -n 2 and see it succeeding - even tho seq wrote way more than 2 lines).

faho avatar May 13 '24 12:05 faho