← Back to context

Comment by mprovost

6 days ago

Handling SIGINT (ctrl-c) with child processes is tricky, but a more pervasive problem for Rust CLI programs is handling SIGPIPE. For historical reasons the compiler adds a signal handler before calling main() which ignores SIGPIPE. It means when you pipe your Rust CLI program's output to something like head, instead of being killed by the signal sent when head closes the pipe, you get a write error and usually print an error message instead of silently dying. You can match on the type of write error and skip printing a message when it's from a broken pipe, but a more subtle problem is that the shell sets the exit status of a program killed by a signal to 128 + the signal number, so 141 in the case of a broken pipe. You can emulate this behaviour by checking for a broken pipe and explicitly exiting with a 141 status code, but it's not possible to fully reproduce being killed by a signal. There's been an issue to make this configurable (the latest proposal is via a compiler flag) for years.

That's good to know; ysh[1] will treat pipelines with a SIGPIPE indicative exit code as successful to allow e.g.

  foo |head -n2

to be treated as successful. I haven't seen anyone complaining yet about rust programs breaking this idiom, but I'll keep an eye out for it.

1: https://oils.pub/release/latest/doc/ysh-tour.html

  • In other shells, the status of the pipeline is the exit status of the last process, so in this case that would be head which exits with 0.

    • Not in e.g. bash with "pipefail" set. However the pipefail option in bash will choke on my previous example, hence treating a SIGPIPE the same as success in ysh.

      1 reply →