[Haskell-cafe] Protecting against main thread exit

Roman Cheplyaka roma at ro-che.info
Mon Feb 17 20:07:58 UTC 2014

* Albert Y. C. Lai <trebla at vex.net> [2014-02-17 14:10:35-0500]
> On 14-02-15 04:53 PM, Roman Cheplyaka wrote:
> >Of course, all guarantees are off when dealing with SIGKILL or power
> >reset, but there's one other annoying exception (no pun intended) — when
> >the main thread terminates, all other threads are silently shut down,
> >without any chance to clean up.
> >
> >Why is it done that way? Why not kill them with an async exception
> >instead? That would be a more uniform behavior.
> >
> >Are there any good ways to protect again the main thread exit?
> >
> >(Note that I'm talking about a library, so I can't have direct
> >control over what's going on in the main thread.)
> I guess it is a rabbit hole to have GHC RTS courteously send
> exceptions to all threads, and still expect certain and timely death.
> Nothing says that a thread voluntarily dies after receiving that
> exception, much less when it dies.

Sure. But is it a responsibility of the RTS to ensure timely death?

My main use case is when the user interrupts the program with Ctrl-C.
The main thread can already ignore it, and the RTS is fine with it.
I'm just proposing that all threads get a chance to clean up.

If timely shutdown is important, there are plenty of external tools with
customizable logic that can take care of that.

> It seems to me that the cleanest way is still to have the author of
> main decide and implement whichever exit strategy is correct for
> his/her main and for the library he/she uses. And there are enough
> tools to do it tidily, too: main can get a "finally" clause, just
> like everybody else.

I wouldn't call that tidy. The action for which cleanup is needed
can be buried deep in the call hierarchy. For example, let's say 'a'
needs to cleanup after doing its job (but no-one needs to wait for a's
result). 'b' spawns 'a' in a new thread, 'c' calls 'b', 'd' calls 'c'
and so on. We cannot cleanup in the "finally" clause of 'b' even if 'b'
is executed in the main thread, because it's okay for 'a' to run longer
than 'b'.

We cannot simply pass the cleanup action up the chain of calls as a
return value (although even that would be rather invasive), because when
SIGINT strikes — say, before 'b' returns, the cleanup handler won't be
installed yet — it will only be on its way to 'main'.

The simplest option seems to be to convert the chain of calls from 'b'
up to 'main' to CPS (directly or by using a monad). I hope you agree
that it is too much for such a simple thing as (more or less) robust

Also, this violates DRY: I have to install the handler both
for the worker thread itself (in case it gets killed by an async
exception), and for the main thread (in case it decides to exit for
whatever reason). Moreover, I have to ensure that the cleanup doesn't
happen twice because of that.


More information about the Haskell-Cafe mailing list