I have a program that indefinitely generates output. I want to sample that output for a second and pipe into gzip. I'm using timeout util to limit the execution but the problem is that gzip gets killed as well.
E.g.:
$ /usr/bin/timeout 1 bash -c "echo asdf; sleep 5" | gzip > /tmp/foo.gz; ls -lah /tmp/foo.gz
Terminated
-rw-rw-r-- 1 haizaar haizaar 0 Jul 22 15:05 /tmp/foo.gz
You see, the gzip command is Terminated and hence its output results in an empty file (due to the lost buffers)
I don't understand how timeout manages to kill a process that reads its stdout; and how to fix it.
Even wrapping the whole thing in another bash results the same:
$ bash -c '/usr/bin/timeout 1 bash -c "echo asdf; sleep 5"' | gzip > /tmp/foo.gz; ls -lah /tmp/foo.gz
Terminated
-rw-rw-r-- 1 haizaar haizaar 0 Jul 22 15:30 /tmp/foo.gz
I can prepend timeout with setsid and then it works which makes me think it's somehow related to process groups being mixed up but it's hard to accept the fact that the current situation is "by design" because it makes timeout command very tricky to use with shell pipes.
Environemnt:
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"
$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ timeout --version
timeout (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Padraig Brady.
UPDATE
KamilCuk was spot on with his strace analysis. It also explains while wrapping timeout in another bash doesn't help either - it seems like bash has an optimization where if it has only command to run it doesn't forks but rather execs replacing itself.
But if you add another command into the wrapping bash then it will fork thus creating a new process group and hence limiting the blast radius of the timeout command. I.e.
bash -c 'true; /usr/bin/timeout 1 bash -c "echo asdf; sleep 5"' | gzip > /tmp/foo.gz
(note the leading true)
I still think using timeout in pipelines is a black magic but that's another story.