0

I'm running Ubuntu 16.04

I have this process X running from multiple tty. I run it from other pseudo terminals using the screen command and it also runs from crontab.

This program is launched from a python script which is launched from a bash script.

Sometimes, the python script gets an exception which I'm not able to catch and it leaves this process X running.

I would like to kill this process X at the end of the bash script but to avoid killing other X processes which are running from other terminals.

I thought of doing pgrep to process X which is not associated with other terminals using the -t argument but I couldn't understand the syntax from the documentation.

1

Start a new process group for the Python code (using the setsid utility) and whatever other processes (including the application proper) it starts, so you can kill the entire process group if needed.

You can use the following construct to do so:

exec 3>&1
pgid=$(exec setsid bash -c 'echo $$; exec >&3-; exec COMMAND...')

where COMMAND... is the command and its parameters you'd normally start. Note that it is within single quotes, and that the command to be run must be evaluatable as a string (as opposed to a normal shell expression).

The first redirection, 3>&1, copies the standard output descriptor to descriptor 3. The redirection >&3- moves descriptor 3 to standard output.

The $(...) executes the ..., and evaluates to the data it wrote to standard output. Above, we read the standard output into shell variable pgid.

The subshell (...) is replaced with the setsid utility, which executes its argument in a new session (process group). Here, it executes bash, which prints out the current process PID ($$), moves the original standard output from descriptor 3 back, and replaces itself/executes the desired COMMAND....

The shell will execute that line for as long as the COMMAND... runs, and will only progress to the next line after COMMAND... itself exits.

If the COMMAND... leaves spurious processes running, and you want to kill them, all you need to do is run

kill -SIGNAL -$pgid

The issue is which signal to send. KILL will immediately kill the leftover processes in the process group, so it is the simplest option. However, if the processes behave nicely, you should be able to ask them to exit by sending them a TERM signal instead. Of course, sometimes the processes may be left in a wonky state, so that they don't react to TERM and do need to be KILLed. To solve this, you can use a small loop, and ps to see if there are any processes left:

retries=50
while ((1)); do

    # Processes left?
    left=$(ps -o pid= -s $pgrp)
    [ -n "$left" ] || break

    # Ask them to terminate.
    kill -TERM $left

    # Wait 0.1 seconds.
    sleep .1

    # Decrement the retry counter.
    ((--retries > 0)) || break
done

# If there are any processes left in the group,
# send them a KILL signal.
left=$(ps -o pid= -s $pgrp)
[ -n "$left" ] && kill -KILL $left

Note that the 50 retries (0.1 seconds each) mean that this waits only up to 5 seconds before sending a kill signal. That may not be a suitable value, depending on the application and the kind of hardware it is running. For example, if the machine is a laptop, and the application saves some history or logs into a number of files, and the drive happens to be sleeping at the exit point, I'd up the delay to perhaps 15 to 30 seconds.

  • Hey, looks like a great solution but I'm not sure I understand how to use the setsid. I need to prepare a bash script and inside call to a python script. I guess I should set the setid for the python script, right? so each process it will start will be in the group. Also, will it work without the redirection? And last thing, if the python script fails, will it still return the pid to pgid? – A.S.F Studio Dec 11 '16 at 14:18
  • @A.S.FStudio: It all depends on the scripts used, and where the failures occur (that lead to superfluous processes left behind). I'd personally try to incorporate the above into the existing scripts -- but if they are too complicated, you could just wrap the existing script around this one. Yes, processes the command starts will be in the new group, unless they start a new session. The redirection is needed to provide the process ID (which turns out to be the process group id) to the outermost shell into pgid; it is "undone" by the time the COMMAND... is started. – Nominal Animal Dec 11 '16 at 17:18
  • @A.S.FStudio: The process group ID is emitted before the command even starts, so it will always be provided. If you omit the redirection, then the output of the scripts will be mixed in with the ID number, and may cause the outer script to fail (say, due to out of memory). You definitely want to keep the redirection! As I said, it is "undone" by the time the COMMAND... is run, so why would you omit it anyway? – Nominal Animal Dec 11 '16 at 17:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.