Disclaimer: This question came up much longer than expected. I split it in 5 sub-questions. I really tried to clarify my mind before opening it, but too many aspects are confusing me at the moment.
Trying to clarify my mind about how to correctly handle processes in Bash in a solid way, I stumbled on this Greg's Wiki article. There, rather at the beginning, there is this statement
If you're still in the parent process that started the child process you want to do something with, that's perfect. You're guaranteed the PID is your child process (dead or alive), for the reasons explained below. You can use
kill
to signal it, terminate it, or just check whether it's still running. You can usewait
to wait for it to end or to get its exit code if it has ended.
Towards the end of the page, the above-mentioned reasons explained below are found.
Each UNIX process also has a parent process. This parent process is the process that started it, but can change to the
init
process if the parent process ends before the new process does. (That is,init
will pick up orphaned processes.) Understanding this parent/child relationship is vital because it is the key to reliable process management in UNIX. A process's PID will NEVER be freed up for use after the process dies UNTIL the parent processwait
s for the PID to see whether it ended and retrieve its exit code. If the parent ends, the process is returned toinit
, which does this for you.This is important for one major reason: if the parent process manages its child process, it can be absolutely certain that, even if the child process dies, no other new process can accidentally recycle the child process's PID until the parent process has
wait
ed for that PID and noticed the child died. This gives the parent process the guarantee that the PID it has for the child process will ALWAYS point to that child process, whether it is alive or a "zombie". Nobody else has that guarantee.Unfortunately, this guarantee doesn't apply to shell scripts. Shells aggressively reap their child processes and store the exit status in memory, where it becomes available to your script upon calling
wait
. But because the child has already been reaped before you callwait
, there is no zombie to hold the PID. The kernel is free to reuse that PID, and your guarantee has been violated.
I read the above paragraphs several times by now, but I am still not sure I am grasping correctly the message behind it.
Question 1: From the second long quote, and in particular from its last paragraph, I would conclude that in shell (I am only interested to Bash) scripts I cannot be 100% sure that a PID I stored in a variable still refers to the background process I started, since it might be reused by the kernel for any other process (even not a child). Is this correct? Where in the system does the above-mentioned guarantee apply?
Question 2: It seems that the last paragraph of the second quote is in contradiction with the first quote. In general, is it true in shell scripts that "If you're still in the parent process that started the child process [...] You're guaranteed the PID is your child process (dead or alive)"?
Question 3: I tried to find other sources around in the web about this topic and, as always, it is hard to distinguish about truth and inaccurate statements. I got some confirmation but also some more doubts. Referring to this and this questions, it seems that the naive way of starting in background a process in a script, storing its PID in a variable, do some stuff and then use the PID in combination with wait
to get its exit code or with kill
to send signals might fail due to reusage by the kernel of the PID. Is there a general recipe?
Question 4: I also found this comment that suggests "to have background (process) store the return code in a file and have parent fetch it from file". Is this the reliable way to go?
Question 5: Are there caveats about the usage of wait -n
? I would think, if I do not explicitly give (potentially reused) PID to wait
, nothing wrong should happen. However, it seems that in Bash v4.4 the -n
option of wait
is useful with job control enabled, set -m
. Is it still the case in Bash v5.0?
Bonus question: This answer says something similar to Greg's Wiki.
There is only one case in which you can safely use the pid to send signals: when the target process is a direct child of the process that will be sending the signal, and the parent has not yet waited on it.
What is a direct child? Is it different from a child?