Re: [Haskell-cafe] Protecting against main thread exit

* Albert Y. C. Lai
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 cleanup. 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. Roman
participants (1)
-
Roman Cheplyaka