Opened 3 years ago

Closed 3 years ago

#10081 closed bug (invalid)

SIGTERM ignored when process has been detached from terminal

Reported by: nakal Owned by: ekmett
Priority: normal Milestone:
Component: Core Libraries Version: 7.8.3
Keywords: Cc: core-libraries-committee@…
Operating System: Unknown/Multiple Architecture: x86_64 (amd64)
Type of failure: Incorrect result at runtime Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

I've tried to write a simple Unix daemon that reacts to signals. Here is the reduced source code sample:

import Control.Concurrent
import Control.Monad
import System.Exit
import System.IO
import System.Posix.Signals

loop = forever $ threadDelay 1000000

main = do
        ppid <- myThreadId
        mapM (\sig -> installHandler sig (Catch $ trap ppid) Nothing)
                [ lostConnection, keyboardSignal, softwareTermination, openEndedPipe ]
        loop

trap tid = do
        hPutStrLn stderr "Signal received.\n"
        throwTo tid ExitSuccess

Such daemons usually run in background without a terminal attached. I verified that I can kill the process after being started on the terminal:

> ./daemon
Signal received.

With:

> killall daemon

in other terminal.

Other test, when I run it in background:

> ./daemon
(press Ctrl+Z)
> bg
> killall daemon
Signal received.

This case is also ok. Now the third test which is also OK:

> ./daemon > log 2>&1
(press Ctrl+Z)
> bg
> exit

In second terminal:

> cat log
> killall daemon
> cat log
Signal received.

> killall daemon
No matching processes belonging to you were found

Now the fourth test which is simply without a log file and this one fails:

> ./daemon
(press Ctrl+Z)
> bg
> exit

On some other terminal try to kill the process:

> killall xxx
> killall xxx
> killall xxx
> killall -9 xxx
> killall xxx
No matching processes belonging to you were found

Signal 9 (SIGKILL) kills the process hard without the signal trap. That's why it works, of course, but SIGTERM is being ignored for some reason. Maybe I am forgetting something, but in my opinion this should end the process properly, too.

Change History (11)

comment:1 Changed 3 years ago by rwbarton

Is openEndedPipe SIGPIPE? Then, in the last test, as you have exited the shell that invoked the daemon, when you send it a SIGTERM, wouldn't the daemon try to print an error message trap, and then receive a SIGPIPE since its stderr is closed, and then enter trap again to handle that signal, etc.?

comment:2 Changed 3 years ago by nakal

As far as I know, SIGPIPE will occur, if a pipe was there, but it's closed later and the main process still tries to write to it. I think detached programs don't get SIGPIPE when they write something to stdout/stderr.

I have to mention some more facts here. I tried to catch many more signals to check what happens. My small program originally did not catch SIGPIPE and the behavior is the same. You can try it.

There is no endless loop when you send the signal. The process is still idle. I think that the main process still works correctly without interruptions, but I will verify it later to be sure.

comment:3 Changed 3 years ago by rwbarton

Okay, I tried running it under strace: the write from hPutStrLn is failing with EIO, which then gets turned into an exception, meaning throwTo tid ExitSuccess is never run. Since signals are handled in a new thread, if the signal handler raises an exception, it is reported to stderr (which, in this case, also fails with EIO), and the program continues to run.

comment:4 Changed 3 years ago by nakal

So the problem is that the exception that tries to output the cause to stderr also runs in the same problem as my trap function. Probably, this is not an intended behavior.

comment:5 in reply to:  3 Changed 3 years ago by pgj

Replying to rwbarton:

Okay, I tried running it under strace

Did you try to diagnose this on FreeBSD, or did you do this on some other system, such as Linux? I am asking because if the problem appears on systems other than FreeBSD, I would like to recommend to change the "Operating System" field of the ticket to reflect that this issue is system independent.

comment:6 Changed 3 years ago by rwbarton

Operating System: FreeBSDUnknown/Multiple

Indeed, I am testing on Linux. However, I don't understand why this behavior is unexpected. The thread in which the signal handler runs dies due to an uncaught exception from hPutStrLn, and so the throwTo is never reached.

comment:7 Changed 3 years ago by nakal

Did you try to catch the exception at hPutStrLn? Maybe I did something wrong, but I could not work around it. Can you paste some code, so I can verify this?

comment:8 Changed 3 years ago by argiopeweb

Cc: core-libraries-committee@… added
Component: libraries/unixCore Libraries
Owner: set to ekmett

comment:9 Changed 3 years ago by rwbarton

nakal: consider this program for instance

import Control.Concurrent
import Control.Exception
import Control.Monad
import System.Exit
import System.IO
import System.Posix.Signals

loop = forever $ threadDelay 1000000

main = do
        ppid <- myThreadId
        mapM (\sig -> installHandler sig (Catch $ trap ppid) Nothing)
                [ keyboardSignal ]
        loop

trap tid = do
        hPutStrLn stderr "Signal received.\n"
        throwIO Overflow
        throwTo tid ExitSuccess

If you run it in a terminal and press ctrl-C, you will see

^CSignal received.

u: arithmetic overflow

and the program will keep running.


To catch the exception from hPutStrLn you can simply do

import Control.Exception
import Control.Concurrent
import Control.Monad
import System.Exit
import System.IO
import System.Posix.Signals

loop = forever $ threadDelay 1000000

main = do
        ppid <- myThreadId
        mapM (\sig -> installHandler sig (Catch $ trap ppid) Nothing)
                [ lostConnection, keyboardSignal, softwareTermination, openEndedPipe ]
        loop

trap tid = do
        let handler :: SomeException -> IO ()
            handler _ = return ()
        catch (hPutStrLn stderr "Signal received.\n") handler
        throwTo tid ExitSuccess

and now the program exits when run and killed as in your fourth test.

comment:10 Changed 3 years ago by nakal

I want to confirm that your solution works. Thank you.

I tried before with bracket. I guess, I messed up something. Also, I find the behavior a bit strange (default ignoring of exceptions in trap handler), but I guess it is OK in this way.

You can close this report. It has been my misunderstanding how exceptions work in signal handlers.

comment:11 Changed 3 years ago by rwbarton

Resolution: invalid
Status: newclosed

I tried before with bracket.

Ah, but bracket reraises the exception after running the cleanup action, so that's why the throwTo was still not reached.

Note: See TracTickets for help on using tickets.