0

There are applications that do not exit cleanly leaving behind their process. One example I've found is Foxit Reader. Of course, the ideal would be to report a bug and have the application fixed, but sometimes that is not possible. For example, with Foxit Reader, the developers indicated they are not working on any fixes to the current version any more as they are doing a complete rewrite.

I would like to have it such that when I exit the application, the process that it leaves behind is automatically closed (without creating more processes that are left behind of course). How do I do that?

mcarans
  • 565
  • If you know the names of potential Foxit orphans, you can scan the process list for them. If their parent has become PID 1, you know they are orphans and kill them. Note however that in more recent kernel versions, orphaned processes can be adopted by another PID than 1 (https://unix.stackexchange.com/a/177361/144217). This could also be your solution: Launch Foxit Reader from a process that configures itself as a Reaper, then have that process deal with Foxit Reader orphans. None of this is really "automatic" in the sense of "set a flag and be done", unfortunately. – berndbausch Jan 29 '21 at 00:37

3 Answers3

1

I believe the simplest way is to start the program via a script and establish a trap to kill the whole process group:

#!/bin/bash
trap "kill -9 -- -$$" ERR EXIT
/path/to/gui-programm

How does it work?

A trap is a shell builtin that execute a certain command if the script receives one (or more) signals. In this case EXIT stands for a graceful exit (i.e. closing the GUI programm) and ERR for any error (e.g. CTRL+C, or if foxit got killed by another reason). Ref: man signal and man bash

The command in use is kill, where -9 sends a SIGKILL. kill may terminate a single process via kill <pid> will act on a whole process group (i.e. process and all its children and forks) by adding a minus to the PID. -- is needed so that the following argument is not interpreted as a flag. I.e. kill -9 -- -$pid will kill a while process group.

$$ is simply the PID of the current shell script. The GUI-program is a child of the script and also child processes of the GUI-program itself are part of the process group linked to the script as the origin.

If you want to test it, I suggest using many sleeps:

 #!/bin/bash
 #test script for kill via trap
 trap "kill -9 -- -$$" ERR EXIT
 for (( i=1 ; i<=100 ; i++ )) ; do
   sleep 120
 done
 /path/to/gui-command

Now if you exit the GUI command, all sleeps will disappear. If you comment the trap part out, they will remain.


In order to ensure that the trap is also executed on failures of child processes, it needs to be inherited via set -E.

#!/bin/bash
set -E
trap "kill -9 -- -$$" ERR EXIT
/path/to/gui-programm
FelixJN
  • 13,566
  • Does this handle the GUI programme not exiting properly? – mcarans Mar 02 '21 at 00:48
  • @mcarans Short: Yes. Long: My tests were with sleep 120 as suggested and pluma, a GUI text editor. I then killed pluma via kill PID and all sleeps were removed. The trick is killing the group. The question is only, which signals you add in the trap. – FelixJN Mar 02 '21 at 12:18
  • I gave it a try using: #!/bin/bash trap "kill -9 -- -$$" ERR EXIT /PATH/opt/foxitsoftware/foxitreader/FoxitReader

    I ran the above (with appropriate PATH) in one terminal and opened another where I did ps -ef. When I closed the Foxit UI, it remained hanging in the terminal (didn't get prompt back) and obviously the process still remained when I did ps -ef. I'm not sure how to make the trap work.

    – mcarans Mar 02 '21 at 23:27
  • Try observating the process tree with ps a --forest. I assume that foxit actually adds a few children. To pass on the trap to subprocesses, too, add set -E after the shebang line. - See update. – FelixJN Mar 03 '21 at 11:14
0

One way to do this is to launch the application from a script instead of directly. The application is run with its output piped to a temporary file. Then tail is run against that file piping to a fifo. Next grep runs against the fifo. When certain text is found, the tail process is killed, the fifo closed, the application process is force killed and the temporary file deleted.

The text should be chosen such that it is the last text output by the application on exit (which you can see if you launch the application from the command line). For example, the last text FoxitReader outputs when I close it is "CJS_Module::ExitEnumJSPlugThread".

The following example script shows how this works with FoxitReader and can be easily adapted for other applications:

#!/bin/bash
file=/tmp/foxit.txt
YOURPATH/foxitsoftware/foxitreader/FoxitReader "$@" &> $file &
foxitpid=$!
fifo=/tmp/tmpfifo.$$
mkfifo $fifo || exit 1
tail -f $file >$fifo &
tailpid=$!
grep -m 1 "CJS_Module::ExitEnumJSPlugThread" $fifo
kill $tailpid
rm $fifo
kill -9 $foxitpid
rm -f $file
exit 0
mcarans
  • 565
0

According to the answer from @mcarans, you can fine-turn the launching script at the Foxit Reader installation directory:

<INSTALLPATH>/foxitreader/FoxitReader.sh

Modify this script to (remember to check the <INSTALLPATH>):

#!/bin/bash
selfpath="<INSTALLPATH>/foxitreader"
file=/tmp/foxit.txt
"$selfpath/FoxitReader" "$@" &> $file &
foxitpid=$!
fifo=/tmp/tmpfifo.$$
mkfifo $fifo || exit 1
tail -f $file >$fifo &
tailpid=$!
grep -m 1 "~CJS_Module()" -m 1 "CJS_Module::ExitEnumJSPlugThread()" $fifo
kill $tailpid
rm $fifo
kill -9 $foxitpid
rm -f $file
exit 0

Save it, and you are all set. You can double-click the PDF file and close it as usual, and there will be no leftover processes.

Andy Ma
  • 101
  • I tried that, but it didn't work for me. The hanging process was not killed when I closed the application. – mcarans Aug 21 '22 at 18:59
  • @mcarans Thanks for letting me know. The trap solution was indeed not very stable as I tested. I modified the script based on your running log solution and added a pre-condition ("~CJS_Module()" should appear before "CJS_Module::ExitEnumJSPlugThread()"). Now it should work with double-clicking the PDF file. – Andy Ma Aug 22 '22 at 20:12
  • Given that the text "CJS_Module::ExitEnumJSPlugThread()" is only output on exit, I don't think the pre-condition is necessary, although it won't harm to include it. – mcarans Aug 24 '22 at 00:38