threads-supervisor- Simple, IO-based library for Erlang-style thread supervision

Safe HaskellSafe-Inferred




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



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.

Different type of 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

Creating a SupervisorSpec

A SupervisorSpec simply holds the state of our supervision, and can be safely shared between supervisors. Under the hood, both the SupervisorSpec and the Supervisor share the same structure; in fact, they are just type synonyms:

type SupervisorSpec = Supervisor_ Uninitialised
type Supervisor = Supervisor_ Initialised

The important difference though, is that the SupervisorSpec does not imply the creation of an asynchronous thread, which the latter does. To keep separated the initialisation of the data structure from the logic of supervising, we use GADTs and type synonyms to force you create a spec first. Creating a spec it just a matter of calling newSupervisorSpec.

Creating a Supervisor

Creating a Supervisor from a SupervisionSpec, is as simple as calling newSupervisor. immediately after doing so, a new thread will be started, monitoring any subsequent IO actions submitted to it.

Supervising and choosing a RestartStrategy

Let's wrap everything together into a full blown example:

main :: IO ()
main = bracketOnError (do
  supSpec <- newSupervisorSpec

  sup1 <- newSupervisor supSpec
  sup2 <- newSupervisor supSpec

  sup1 `monitor` sup2

  _ <- forkSupervised sup2 OneForOne job3

  j1 <- forkSupervised sup1 OneForOne job1
  _ <- forkSupervised sup1 OneForOne (job2 j1)
  _ <- forkSupervised sup1 OneForOne job4
  _ <- forkIO (go (eventStream sup1))
  return sup1) shutdownSupervisor (\_ -> threadDelay 10000000000)
   go eS = do
     newE <- atomically $ readTBQueue eS
     print newE
     go eS

What we have done here, was to spawn our supervisor out from a spec, any using our swiss knife forkSupervised to spawn for supervised IO computations. As you can see, if we partially apply forkSupervised, its type resemble forkIO one; this is by design, as we want to keep this API as IO-friendly as possible in the very same example, we also create another supervisor (from the same spec, but you can create a separate one as well) and we ask the first supervisor to monitor the second one.

If you run this program, hopefully you should see on stdout something like this:

ChildBorn ThreadId 62 2015-02-13 11:51:15.293882 UTC
ChildBorn ThreadId 63 2015-02-13 11:51:15.293897 UTC
ChildBorn ThreadId 64 2015-02-13 11:51:15.293904 UTC
ChildDied ThreadId 61 (MonitoredSupervision ThreadId 61) 2015-02-13 11:51:15.293941 UTC
ChildBorn ThreadId 65 2015-02-13 11:51:15.294014 UTC
ChildFinished ThreadId 64 2015-02-13 11:51:18.294797 UTC
ChildDied ThreadId 63 thread killed 2015-02-13 11:51:18.294909 UTC
ChildDied ThreadId 62 Oh boy, I'm good as dead 2015-02-13 11:51:20.294861 UTC
ChildRestarted ThreadId 62 ThreadId 68 OneForOne 2015-02-13 11:51:20.294861 UTC
ChildFinished ThreadId 65 2015-02-13 11:51:22.296089 UTC
ChildDied ThreadId 68 Oh boy, I'm good as dead 2015-02-13 11:51:25.296189 UTC
ChildRestarted ThreadId 68 ThreadId 69 OneForOne 2015-02-13 11:51:25.296189 UTC
ChildDied ThreadId 69 Oh boy, I'm good as dead 2015-02-13 11:51:30.297464 UTC
ChildRestarted ThreadId 69 ThreadId 70 OneForOne 2015-02-13 11:51:30.297464 UTC
ChildDied ThreadId 70 Oh boy, I'm good as dead 2015-02-13 11:51:35.298123 UTC
ChildRestarted ThreadId 70 ThreadId 71 OneForOne 2015-02-13 11:51:35.298123 UTC

Wrapping up

I hope that you are now convinced that this library can be of some use to you!