PoolToy: A (toy) process pool manager in Elixir 1.6 (part 2.2)

Implementing multiple pools

(This post is part of a series on writing a process pool manager in Elixir.)

In the last post, we did  most of the work enabling us to have multiple pools within PoolToy. But before we jump into the implementation, let’s take a moment to think about fault tolerance: how should pool B be affected if pool A has a problem and crashes?

Well, the whole point of PoolToy is to keep those 2 pools independent: a crash in one of them shouldn’t affect the other in any way. At the same time, we’ve decreed that the pool manager and work supervisor are co-dependent and should therefore crash together: that’s why we’ve set the pool supervisor’s restart strategy to :one_for_all (if the pool manager dies, we need the pool supervisor to kill off the worker supervisor, so a :one_for_one strategy wouldn’t be a good fit).

Right, so within a pool, we need a :one_for_all strategy, but between pools we need :one_for_one. Therefore, we’ll need to introduce another level of supervision: a pools supervisor (note the plural: not to be confused with the singular pool supervisor) will supervise the various individual pool supervisors.

Adding PoolsSup

Here’s our new PoolsSup module (lib/pool_toy/pools_sup.ex):

defmodule PoolToy.PoolsSup do
  use DynamicSupervisor

  @name __MODULE__

  def start_link(opts) do
    DynamicSupervisor.start_link(__MODULE__, opts, name: @name)

  def init(_opts) do
    DynamicSupervisor.init(strategy: :one_for_one)

Since we’re now going to handle starting pools through the pools supervisor, that’s what our application module should start instead of a pool supervisor (lib/pool_toy/application.ex):

defmodule PoolToy.Application do
  use Application

  def start(_type, _args) do
    children = [

    opts = [strategy: :one_for_one]
    Supervisor.start_link(children, opts)

On line 6, we’re no longer directly starting a pool supervisor, but start the pools supervisor. And since we’ve now got a statically named process, let’s go ahead and register that on lines 4-6 (mix.exs):

def application do
    mod: {PoolToy.Application, []},
    registered: [
    extra_applications: [:logger]

Let’s check everything works properly: start an IEx session with iex -S mix. If you take a look at the Observer (launch it with :observer.start(), remember?) you’ll that no pools were started, because our application file no longer launches a pool on startup, it only starts the pools supervisor.

But we can take our code for a spin by starting a new pool ourselves: the pools supervisor is a DynamicSupervisor, so we can use DynamicSupervisor.start_child/2 to do so:

    {PoolToy.PoolSup, [name: :poolio, worker_spec: Doubler, size: 3]})

Calling that manually is a bit of a pain, especially since we’re expecting the client to magically know he’s dealing with a DynamicSupervisor. Let’s clean that up by adding PoolsSup.start_pool/1 (lib/pool_toy/pools_sup.ex):

def start_pool(args) do
  DynamicSupervisor.start_child(@name, {PoolToy.PoolSup, args})

def init(_opts) do
  # edited for brevity

If you now start a new IEx session, you can start a new pool with

iex(1)> PoolToy.PoolsSup.start_pool(name: :poolio, worker_spec: Doubler, size: 3)

Let’s make that even better by exposing our public API through the PoolToy module (lib/pool_toy.ex):

defmodule PoolToy do
  defdelegate start_pool(args), to: PoolToy.PoolsSup

Note the generated code previously in the PoolToy module has been completely removed. As you can tell from the docs (and probably its name), defdelegate/2 simply defines a function on the current module that will delegate the call to a function defined in another module.

Our changes so far

And now we can start pools in an even cleaner fashion:

iex(1)> PoolToy.start_pool(name: :poolio, worker_spec: Doubler, size: 3)

Great! Let’s start another pool, since it’s so easy!

iex(2)> PoolToy.start_pool(name: :pooly, worker_spec: Doubler, size: 3)
  {:failed_to_start_child, PoolToy.PoolMan,
      {:ets, :new, [:monitors, [:protected, :named_table]], []},
      {PoolToy.PoolMan, :init, 2, [file: 'lib/pool_toy/pool_man.ex', line: 70]},
      {:gen_server, :init_it, 2, [file: 'gen_server.erl', line: 374]},
      {:gen_server, :init_it, 6, [file: 'gen_server.erl', line: 342]},
      {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}

Dang. It was too good to be true, wasn’t it? Try figuring out the problem on your own, and join me in next post to fix multiple pool creation.

Would you like to see more Elixir content like this? Sign up to my mailing list so I can gauge how much interest there is in this type of content.

This entry was posted in Elixir. Bookmark the permalink.