{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-| Use @threads-supervisor@ if you want the "poor-man's Erlang supervisors". @threads-supervisor@ is an IO-based library with minimal dependencies which does only one thing: It provides you a 'Supervisor' entity you can use to monitor your forked computations. If one of the managed threads dies, you can decide if and how to restart it. This gives you: * Protection against silent exceptions which might terminate your workers. * A simple but powerful way of structure your program into a supervision tree, where the leaves are the worker threads, and the nodes can be other supervisors being monitored. * A disaster recovery mechanism. You can install the @threads-supervisor@ library by running: > $ cabal install threads-supervisor -} module Control.Concurrent.Supervisor.Tutorial ( -- * Introduction -- $introduction -- * Different type of jobs -- $jobs -- * Creating a SupervisorSpec -- $createSpec -- * Creating a Supervisor -- $createSupervisor -- * Supervising and choosing a 'RestartStrategy' -- $supervising -- * Wrapping up -- $conclusions ) where -- $introduction -- Who worked with Haskell's concurrency primitives would be surely familiar -- with the `forkIO` function, which allow us to fork an IO computation in a separate -- green thread. `forkIO` is great, but is also very low level, and has a -- couple of subtleties, as you can read from this passage of the documentation: -- -- The newly created thread has an exception handler that discards the exceptions -- `BlockedIndefinitelyOnMVar`,`BlockedIndefinitelyOnSTM`, and `ThreadKilled`, -- and passes all other exceptions to the uncaught exception handler. -- -- To mitigate this, we have a couple of libraries available, for example -- and . -- -- But what about if I do not want to take explicit action, but instead specifying upfront -- how to react to disaster, and leave the library work out the details? -- This is what this library aims to do. -- $jobs -- In this example, let's create four different threads: -- -- > job1 :: IO () -- > job1 = do -- > threadDelay 5000000 -- > fail "Dead" -- This job will die after five seconds. -- -- > job2 :: ThreadId -> IO () -- > job2 tid = do -- > threadDelay 3000000 -- > killThread tid -- -- This other job instead, we have waited three seconds, and then kill a target thread, -- generating an asynchronous exception. -- -- > job3 :: IO () -- > job3 = do -- > threadDelay 5000000 -- > error "Oh boy, I'm good as dead" -- -- This guy is very similar to the first one, except for the fact `error` is used instead of `fail`. -- -- > job4 :: IO () -- > job4 = threadDelay 7000000 -- @job4@ is what we wish for all our passing cross computation: smooth sailing. -- -- These jobs represent a significant pool of our everyday computations in the IO monad -- $createSpec -- Here I discuss the creation of a 'SupervisorSpec' -- $createSupervisor -- Here I discuss the creation of a 'Supervisor' from a 'SupervisionSpec' -- $supervising -- Here I discuss how you can supervise other threads. -- $conclusions -- I hope that you are now convinced that this library can be of some use to you! -- -- > main :: IO () -- > main = bracketOnError (do -- > supSpec <- newSupervisor -- > sup <- supervise supSpec -- > j1 <- forkSupervised sup OneForOne job1 -- > _ <- forkSupervised sup OneForOne (job2 j1) -- > _ <- forkSupervised sup OneForOne job3 -- > _ <- forkSupervised sup OneForOne job4 -- > _ <- forkIO (go (eventStream sup)) -- > return sup) shutdownSupervisor (\_ -> threadDelay 10000000000) -- > where -- > go eS = do -- > newE <- atomically $ readTBQueue eS -- > print newE -- > go eS