How are exceptions delivered in a concurrent Haskell program?
Let's say we have a process, with multiple threads and one of them is interacting with something over a TCP connection and we get a signal (say on a *nix system).
Which (green)thread will this signal be delivered to?
Will it be delivered to the one which is "using" the socket or a "main" designated thread will receive and it has to explicitly do a throwTo
to send the exception to that (green)thread?
Concurrency means implementing a program by using multiple I/O-performing threads. While a concurrent Haskell program can run on a parallel machine, the primary goal of using concurrency is not to gain performance, but rather because that is the simplest and most direct way to write the program.
There are three different ways exceptions can be thrown in Haskell: Synchronously thrown: an exception is generated from IO code and thrown inside a single thread. Asynchronously thrown: an exception is thrown from one thread to another thread to cause it to terminate early.
Parallelism and Concurrency in HaskellHaskell supports both pure parallelism and explicit concurrency.
Haskell threads are much more efficient in terms of both time and space than Operating System threads. Apart from traditional synchronization primitives like semaphores, Haskell offers Software Transactional Memory which greatly simplifies concurrent access to shared memory. The modules for concurrency are Control.
There is no automatic correspondence between POSIX signals and Haskell exceptions.
In order for a signal to be turned into an exception, there needs to be a signal handler that will do just that — throw an exception to one of the Haskell threads. Which thread gets the exception depends entirely on how the signal handler is set up.
By default, GHC sets up such a handler for SIGINT only, and that signal delivers the exception to the main thread.
You can install similar handlers for the other signals:
import Control.Concurrent (mkWeakThreadId, myThreadId) import Control.Exception (Exception(..), throwTo) import Control.Monad (forM_) import Data.Typeable (Typeable) import System.Posix.Signals import System.Mem.Weak (deRefWeak) newtype SignalException = SignalException Signal deriving (Show, Typeable) instance Exception SignalException installSignalHandlers :: IO () installSignalHandlers = do main_thread_id <- myThreadId weak_tid <- mkWeakThreadId main_thread_id forM_ [ sigABRT, sigBUS, sigFPE, sigHUP, sigILL, sigQUIT, sigSEGV, sigSYS, sigTERM, sigUSR1, sigUSR2, sigXCPU, sigXFSZ ] $ \sig -> installHandler sig (Catch $ send_exception weak_tid sig) Nothing where send_exception weak_tid sig = do m <- deRefWeak weak_tid case m of Nothing -> return () Just tid -> throwTo tid (toException $ SignalException sig) main = do installSignalHandlers ...
But you can also get creative and change the handler to deliver the signal to some other thread. E.g. if there is only one thread working with sockets, you can make sure that thread gets the exception. Or, depending on the signal you're interested in, maybe you can figure out the relevant thread based on the siginfo_t
structure (see sigaction(2)
) — perhaps the si_fd field?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With