help-make
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Questions about signal handling in GNU Make


From: Masahiro Yamada
Subject: Questions about signal handling in GNU Make
Date: Sat, 24 Jul 2021 01:48:01 +0900

Hi.


GNU Make deletes partially updated target files
when it is interrupted.

I am interested in this behavior.
I did not look into the source code,
but did some experiments to understand how GNU
Make behaves.

Ctrl-C is a normal way to terminate GNU Make.
However, the INT signals will be sent to all
the fore-ground processes almost at the same time.
This makes it difficult to understand how it
is working behind the scenes.

So, I sent the INT signal to each process
by running 'kill -INT <PID>' individually.



[Sample code]

$ cat Makefile
test.txt:
        ./create-file.sh $@

$ cat create-file.sh
#!/bin/bash
echo hello > $1
sleep 30
echo bye >> $1



[Test]

$ rm -f test.txt
$ make
./create-file.sh test.txt

# Here, open another terminal.
# Run 'ps aux' to see the process ID of Make.
# Run 'kill -INT <PID-of-Make>
# These must be done within 30 secs.
# Then you will see

make: *** Deleting file 'test.txt'

But, GNU Make does not exit immediately;
It waits until the child process finishes.
After the child process exits, GNU Make
exits as well.


As far as I tested, GNU Make
handles 5 signals (HUP, INT, QUIT, USR1, TERM)
for the automatic target deletion, but in 3
different manners.


[1] HUP, INT, QUIT

When GNU Make receives the INT signal,
it shows the "*** Deleting file ..." message
and deletes the target file.

It waits until the child process finishes
presumably by running the 'wait' syscall.

If the child exits by a signal
(i.e. WIFSIGNALED flag is set), GNU Make shows
"make: *** [Makefile:2: test.txt] Interrupt"
message, and exits.
Otherwise (i.e. if the child exits normally),
GNU Make exits without any further message.

HUP and QUIT work in a similar way.


[2] USR1

When GNU Make receives the USR1 signal,
it does not do anything at this moment.
(but GNU Make remembers it)
GNU Make waits until the child process exits.

If the child process exits with a signal,
GNU Make deletes the target and exits.

If the child exits without a signal,
GNU Make shows the message
"Successfully remade target file ...' and
keeps the target file.


[3] TERM

When GNU Make receives the TERM signal,
it immediately deletes the target file
and exits without waiting for the child process.

The child process is still running, and re-parented.





As a comparison, I wrote a shell script,
which deletes the target file when it is interrupted.

[Sample code 2]

$ cat foo.sh
#!/bin/bash
set -e
trap "echo Deleting file test.txt; rm -f test.txt" INT
./create-file.sh test.txt
echo "exiting shell script"


[Test 2]

$ ./foo.sh

# Here, open another terminal.
# Run 'ps aux' to see the process ID of the './foo.sh'.
# Run 'kill -INT <PID-of-foo.sh>'

Nothing happens at this moment.

After the child process './create-file.sh test'
finishes, you will see the "Deleting file test.txt".

When Bash receives the INT signal, it just
remembers the fact that it has receives the signal,
then invokes the trapping handler _after_ the child
process finishes.

To understand how signals are handled by shells,
https://www.cons.org/cracauer/sigint.html
is a really interesting material.

Bash handles the INT signal in WCE
(Wait and Cooperative Exit) manner by default.
If and only if the child fore-ground process
finished by the INT handler (i.e. WIFSIGNALED=1,
WTERMSIG=2), the bash script is also terminated.

If a shell script traps the INT signal,
it delays the handler until the current fore-ground
process finishes.

This avoids the race between
  - the INT signal handler
  - the child process currently running

Of course, it is implementation-dependent,
and a different shell may behave differently.

For example, another popular shell, dash,
handles the INT signal in WUE (Wait and
Unconditional Exit) manner.



Here, my questions:

(Q1) As you see in the first test,
GNU Make deletes the target file before
the child process finishes.
I think, if the child process writes
to the target file after that, the target
file might be left over.

In fact, the target file 'test.txt' is
left over in the example above because
'create-file.sh' writes the 'bye' message
at the last moment.


(Q2) If my worry is right, is it a good idea
to change the behavior like shells
in order to avoid the race?

When GNU Make receives a signal, it just
remembers that. After the child processes
are all finished, it deletes the targets
that could be partially updated.

(Q3) Why are there 3 different ways to handle signals?


(Q4) After GNU Make receives HUP, INT, QUIT, TERM signals,
    it gets back the handler to SIG_DFL, and sends the
    same signal to itself.
    With this, it can properly propagate WIFSIGNALED
    and WTERMSIG to the upper process.

    In contrast, when GNU Make receives the USR1 signal,
    it does not set WIFSIGNALED. So, from the upper
    process, it looks as if it exited normally.

    Is this an intended behavior, or should it be
    corrected?


--
Best Regards
Masahiro Yamada



reply via email to

[Prev in Thread] Current Thread [Next in Thread]