pool_sup v0.2.2 PoolSup
This module defines a supervisor process that is specialized to manage pool of workers.
Features
- Process defined by this module behaves as a
:simple_one_for_one
supervisor. - Worker processes are spawned using a callback module that implements
PoolSup.Worker
behaviour. PoolSup
process manages which worker processes are in use and which are not.PoolSup
automatically restart crashed workers.- Functions to request pid of an available worker process:
checkout/2
,checkout_nonblocking/2
. - Run-time configuration of pool size:
change_capacity/3
. - Load-balancing using multiple pools:
PoolSup.Multi
.
Example
Suppose we have a module that implements both GenServer
and PoolSup.Worker
behaviours
(PoolSup.Worker
behaviour requires only 1 callback to implement, start_link/1
).
iex(1)> defmodule MyWorker do
...(1)> @behaviour PoolSup.Worker
...(1)> use GenServer
...(1)> def start_link(arg) do
...(1)> GenServer.start_link(__MODULE__, arg)
...(1)> end
...(1)> # definitions of gen_server callbacks...
...(1)> end
When we want to have 3 worker processes that run MyWorker
server:
iex(2)> {:ok, pool_sup_pid} = PoolSup.start_link(MyWorker, {:worker, :arg}, 3, 0, [name: :my_pool])
Each worker process is started using MyWorker.start_link({:worker, :arg})
.
Then we can get a pid of a child currently not in use:
iex(3)> worker_pid = PoolSup.checkout(:my_pool)
iex(4)> do_something(worker_pid)
iex(5)> PoolSup.checkin(:my_pool, worker_pid)
Don’t forget to return the worker_pid
when finished; for simple use cases PoolSup.transaction/3
comes in handy.
Reserved and on-demand worker processes
PoolSup
defines the following two parameters to control capacity of a pool:
reserved
(3rd argument ofstart_link/5
): Number of workers to keep alive.ondemand
(4th argument ofstart_link/5
): Maximum number of workers that are spawned on-demand.
In short:
{:ok, pool_sup_pid} = PoolSup.start_link(MyWorker, {:worker, :arg}, 2, 1)
w1 = PoolSup.checkout_nonblocking(pool_sup_pid) # => pre-spawned worker pid
w2 = PoolSup.checkout_nonblocking(pool_sup_pid) # => pre-spawned worker pid
w3 = PoolSup.checkout_nonblocking(pool_sup_pid) # => newly-spawned worker pid
nil = PoolSup.checkout_nonblocking(pool_sup_pid)
PoolSup.checkin(pool_sup_pid, w1) # `w1` is terminated
PoolSup.checkin(pool_sup_pid, w2) # `w2` is kept alive for the subsequent checkout
PoolSup.checkin(pool_sup_pid, w3) # `w3` is kept alive for the subsequent checkout
Usage within supervision tree
The following code snippet spawns a supervisor that has PoolSup
process as one of its children:
chilldren = [
...
Supervisor.Spec.supervisor(PoolSup, [MyWorker, {:worker, :arg}, 5, 3]),
...
]
Supervisor.start_link(children, [strategy: :one_for_one])
The PoolSup
process initially has 5 workers and can temporarily have up to 8.
All workers are started by MyWorker.start_link({:worker, :arg})
.
You can of course define a wrapper function of PoolSup.start_link/4
and use it in your supervisor spec.
Summary
Functions
Changes capacity (number of worker processes) of a pool
Checks in an in-use worker pid and make it available to others
Checks out a worker pid that is currently not used
Checks out a worker pid in a nonblocking manner, i.e. if no available worker found this returns nil
See PoolSup.CustomSupHelper.format_status/2
Starts a PoolSup
process linked to the calling process
Query current status of a pool
See PoolSup.CustomSupHelper.terminate/2
Checks out a worker pid, executes the given function using the pid, and then checks in the pid
Types
options :: [{:name, GenServer.name}]
pool :: pid | GenServer.name
Functions
Specs
change_capacity(pool, nil | non_neg_integer, nil | non_neg_integer) :: :ok
Changes capacity (number of worker processes) of a pool.
new_reserved
and/or new_ondemand
parameters can be nil
; in that case the original value is kept unchanged
(i.e. PoolSup.change_capacity(pool, 10, nil)
replaces only reserved
value of pool
).
On receipt of change_capacity
message, the pool adjusts number of children according to the new configuration as follows:
- If current number of workers are less than
reserved
, the pool spawns new workers to ensurereserved
workers are available. Note that, as is the same throughout the OTP framework, spawning processes under a supervisor is synchronous operation. Therefore increasingreserved
too many at once may make the pool unresponsive for a while. - When increasing maximum capacity (
reserved + ondemand
) and if any client process is being checking-out in a blocking manner, then the newly-spawned process is returned to the client. - When decreasing capacity, the pool tries to shutdown extra workers that are not in use. Processes currently in use are never interrupted. If number of in-use workers is more than the desired capacity, terminating further is delayed until any worker process is checked in.
Specs
checkin(pool, pid) :: :ok
Checks in an in-use worker pid and make it available to others.
Specs
checkout(pool, timeout) :: pid
Checks out a worker pid that is currently not used.
If no available worker process exists, the caller is blocked until either
- any process becomes available, or
- timeout is reached.
Specs
checkout_nonblocking(pool, timeout) :: nil | pid
Checks out a worker pid in a nonblocking manner, i.e. if no available worker found this returns nil
.
Specs
start_link(module, term, non_neg_integer, non_neg_integer, options) :: GenServer.on_start
Starts a PoolSup
process linked to the calling process.
Arguments
worker_module
is the callback module ofPoolSup.Worker
.worker_init_arg
is the value passed toworker_module.start_link/1
callback function.reserved
is the number of workers thisPoolSup
process holds.ondemand
is the maximum number of workers that are spawned on checkouts when all reserved processes are in use.- Currently only
:name
option for name registration is supported.
Specs
status(pool) :: %{reserved: nni, ondemand: nni, children: nni, available: nni, working: nni} when nni: non_neg_integer
Query current status of a pool.
Specs
transaction(pool, (pid -> a), timeout) :: a when a: term
Checks out a worker pid, executes the given function using the pid, and then checks in the pid.
The timeout
parameter is used only in the checkout step; time elapsed during other steps are not counted.