View Source Workers and callers implementations

Poolex operates with two concepts: callers and workers. In both cases, we are talking about processes.

Callers

Callers are processes that have requested to get a worker (used run/3). Each pool keeps the information about callers to distribute workers to them when they are free.

Caller's typespec

Caller's typespec is GenServer.from() not a pid().

The implementation of the caller storage structure should be conceptually similar to a queue since by default we want to give workers in the order they are requested. But this logic can be easily changed by writing your implementation.

Behaviour of callers collection described here.

Behaviour callbacks

CallbackDescription
init/0Returns state (any data structure) which will be passed as the first argument to all other functions.
add/2Adds caller to state and returns a new state.
empty?/1Returns true if the state is empty, false otherwise.
pop/1Removes one of the callers from state and returns it as {caller, state}. Returns :empty if the state is empty.
remove_by_pid/2Removes given caller by caller's pid from state and returns a new state.
to_list/1Returns list of callers.

Callers implementations out of the box

ModuleDescriptionSourceDefault?
Poolex.Callers.Impl.ErlangQueueFIFO implementation based on :erlang.queuelink

Workers

Workers are processes launched in a pool. Poolex works with two collections of workers:

  1. IdleWorkers -- Free processes that can be given to callers upon request.
  2. BusyWorkers -- Processes that are currently processing the caller's request.

For both cases, the default implementation is based on lists. But it is possible to set different implementations for them.

Behaviour of workers collection described here.

Behaviour callbacks

CallbackDescription
init/0Returns state (any data structure) which will be passed as the first argument to all other functions.
init/1Same as init/0 but returns state initialized with a passed list of workers.
add/2Adds worker's pid to state and returns a new state.
member?/2Returns true if given worker contained in the state, false otherwise.
remove/2Removes given worker from state and returns new state.
count/1Returns the number of workers in the state.
to_list/1Returns list of workers pids.
empty?/1Returns true if the state is empty, false otherwise.
pop/1Removes one of workers from state and returns it as {caller, state}. Returns :empty if the state is empty.

Workers implementations out of the box

ModuleDescriptionSourceDefault?
Poolex.Workers.Impl.ListLIFO implementation based on Elixir's Listlink
Poolex.Workers.Impl.ErlangQueueFIFO implementation based on :erlang.queuelink

Writing custom implementations

It's quite simple when using the Behaviours mechanism in Elixir.

For example, you want to define a new implementation for callers. To do this, you need to create a module that inherits the Poolex.Callers.Behaviour and implement all its functions.

defmodule MyApp.MyAmazingCallersImpl do
  @behaviour Poolex.Callers.Behaviour

  def init, do: {}
  def add(state, caller), do: #...
end

If you have any ideas about what implementations can be added to the library or how to improve existing ones, then please create an issue!

Configuring custom implementations

After that, you need to provide your module names to Poolex initialization:

Poolex.child_spec(
  pool_id: :some_pool,
  worker_module: SomeWorker,
  workers_count: 10,
  waiting_callers_impl: MyApp.MyAmazingCallersImpl
)

That's it! Your implementation will be used in launched pool.

The configuration for workers might look like this:

Poolex.child_spec(
  pool_id: :some_pool,
  worker_module: SomeWorker,
  workers_count: 10,
  busy_workers_impl: MyApp.PerfectBusyWorkersImpl,
  idle_workers_impl: MyApp.FancyIdleWorkersImpl
)