Why would Ctrl-C behave differently than kill -2

killsignals

I have a program which is supposed to handle SIGINT and gracefully shut down. When I run this program from a terminal without backgrounding it I can shut it down just fine using Ctrl-C. Inspecting the logs shows that everything worked as expected.

When I open a separate terminal and call kill -2 [pid] or kill -s INT [pid] it does nothing. I see nothing in the logs and the program continues to run as usual until I hit Ctrl-C in the terminal I launched it from.

Are there any differences between how Ctrl-C sends the signal and how kill does?

Additional Details:

The program in question is a Java application launched by a bash shell script which sets up some environment variables (namely CLASSPATH) and then calls java [main class]. Hitting Ctrl-Z and then running ps results in the following:

$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
mdeck    10251 10250  0 11:48 pts/2    00:00:00 -bash
mdeck    13405 10251  0 18:12 pts/2    00:00:00 /bin/bash /usr/local/bin/myapp.sh
mdeck    13509 13405 25 18:12 pts/2    00:00:03 java com.company.MyApp
mdeck    13526 10251  0 18:13 pts/2    00:00:00 ps -f

The output of stty as requested by Gilles is below:

$ stty -a </dev/pts/2
speed 38400 baud; rows 40; columns 203; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

Best Answer

One possibility is that the program catches the Ctrl+C sequence. Check the output of stty -a; the intr setting indicates which key combination (if any) sends a SIGINT signal, and the isig indicates whether the signal keys are enabled (-isig means they are disabled).

If the program consists of several processes, pressing Ctrl+C sends SIGINT to all the processes in the process group. You can obtain the same effect by sending the signal to the process group instead of sending it to one of the processes. To send a signal to a process group, first determine its leader: that's the first process that starts all the others; if you run the process group in the background, that's the PID shown by jobs -l. The PID of the process group leader is the PGID (process group id); send the signal to its negative. E.g. if the PGID is 1234, run kill -INT -1234.

If the program consists of a wrapper script and a main application, there are two cases to consider. If there is no cleanup to do, so that the wrapper script terminates as soon as the main aplication terminates, make the wrapper script call exec:

#!/bin/sh
export SOMEVAR=somevalue
…
exec /path/to/application "$@"

This way the application replaces the script, inheriting its PID. Some shells optimize a script that ends by executing another program, but not all. This approach doesn't work when the script needs to perform some cleanup such as removing temporary files.

Consider making the script detect a signal and transmitting the signal to the application. Here's a sketch of how this goes:

/path/to/application "$@" &
app_pid=$!
trap -INT 'kill -INT $app_pid'
wait $!
rm /temp/file