Load balancing functions based on Gproc.
Authors: Ulf Wiger (ulf@wiger.net
).
This module implements support for load-balancing server pools. It was originally intended mainly as an example of how to use various Gproc resources (e.g. counters and shared properties), but is fully integrated into Gproc, and fully functional.
Each pool has a list of 'named' workers (defined using add_worker/2
) and
a load-balancing strategy. Processes can then 'connect' to the pool (with
connect_worker/2
), using one of the defined names.
Users then 'pick' one of the currently connected processes in the pool. Which process is picked depends on the load-balancing strategy.
The whole representation of the pool and its connected workers is in gproc.
The server gproc_pool
is used to serialize pool management updates, but
worker selection is performed entirely in the calling process, and can be
performed by several processes concurrently.
round_robin
is the default. A wrapping gproc counter keeps track of the
latest worker picked, and gproc:next()
is used to find the next worker.random
picks a random worker from the pool.hash
requires a value (pick/2
), and picks a worker based on the hash of
that value.direct
takes an integer as an argument, and picks the next worker (modulo
the size of the pool). This is mainly for implementations that implement
a load-balancing strategy on top of gproc_pool
.claim
picks the first available worker and 'claims' it while executing
a user-provided fun. This means that the number of concurrently executing
jobs will not exceed the size of the pool.active_workers/1 | Return a list of currently connected workers in the pool. |
add_worker/2 | Assign a worker name to the pool, returning the worker's position. |
add_worker/3 | Assign a worker name to a given slot in the pool, returning the slot. |
claim/2 | Equivalent to claim(Pool, F, nowait). |
claim/3 | Picks the first available worker in the pool and applies Fun . |
connect_worker/2 | Connect the current process to Name in Pool . |
defined_workers/1 | Return a list of added workers in the pool. |
delete/1 | Delete an existing pool. |
disconnect_worker/2 | Disconnect the current process from Name in Pool . |
force_delete/1 | Forcibly remove a pool, terminating all active workers. |
log/1 | Update a counter associated with a worker name. |
new/1 | Equivalent to new(Pool, round_robin, []). |
new/3 | Create a new pool. |
pick/1 | Pick a worker from the pool given the pool's load-balancing algorithm. |
pick/2 | Pick a worker from the pool based on Value . |
pick_worker/1 | Pick a worker pid from the pool given the pool's load-balancing algorithm. |
pick_worker/2 | Pick a worker pid from the pool given the pool's load-balancing algorithm. |
ptest/4 | |
randomize/1 | Randomizes the "next" pointer for the pool. |
remove_worker/2 | Remove a previously added worker. |
setup_test_pool/4 | |
test_run0/2 | |
whereis_worker/2 | Look up the pid of a connected worker. |
worker_id/2 | Return the unique gproc name corresponding to a name in the pool. |
worker_pool/1 | Return a list of slots and/or named workers in the pool. |
active_workers(Pool::any()) -> [{Name, Pid}]
add_worker(Pool::any(), Name::any()) -> integer()
add_worker(Pool::any(), Name::any(), Slot::integer()) -> integer()
claim(Pool, F::Fun, Wait) -> {true, Res} | false
Pool = any()
Fun = function()
Wait = nowait | {busy_wait, integer()}
Picks the first available worker in the pool and applies Fun
.
A claim
pool allows the caller to "claim" a worker during a short span
(essentially, a lock is set and released as soon as Fun
returns).
Once a worker is selected, Fun(Name, Pid)
is called, where Name
is a
unique gproc name of the worker, and Pid
is its process identifier.
The gproc name of the worker serves as a mutex, where its value is 0 (zero)
if the worker is free, and 1 (one) if it is busy. The mutex operation is
implemented using gproc:update_counter/2
.
Wait == nowait
means that the call will return false
immediately if
there is no available worker.
Wait == {busy_wait, Timeout}
will keep repeating the claim attempt
for Timeout
milliseconds. If still no worker is available, it will
return false
.
connect_worker(Pool::any(), Name::any()) -> true
defined_workers(Pool::any()) -> [{Name, Pos, Count}]
delete(Pool::any()) -> true
disconnect_worker(Pool, Name) -> true
force_delete(Pool::any()) -> true
log(X1::GprocKey) -> integer()
new(Pool::any()) -> ok
new(Pool::any(), Type, Opts) -> true
Type = round_robin | random | hash | direct | claim
Opts = [{size, integer()} | {auto_size, boolean()}]
Create a new pool.
The pool starts out empty. If a size is not given, the pool size is set to
0 initially. auto_size
is true
by default if size is not specified, but
false
by default otherwise. If auto_size == true
, the pool will be
enlarged to accommodate new workers, when necessary. Otherwise, trying to add
a worker when the pool is full will raise an exception, as will trying to add
a worker on a specific position beyond the current size of the pool.
If the given pool already exists, this function will raise an exception.
pick(Pool::any()) -> GprocName | false
Pick a worker from the pool given the pool's load-balancing algorithm.
The pool types that allows picking without an extra argument are
round_robin and random. This function returns false
if there is no available
worker, or if Pool
is not a valid pool.
pick(Pool::any(), Value::any()) -> GprocName | false
Pick a worker from the pool based on Value
.
The pool types that allows picking based on an extra argument are
hash and direct. This function returns false
if there is no available
worker, or if Pool
is not a valid pool.
If the pool is of type direct
, Value
must be an integer corresponding to
a position in the pool (modulo the size of the pool). If the type is
hash
, Value
may be any term, and its hash value will serve as a guide for
selecting a worker.
pick_worker(Pool::any()) -> pid() | false
Pick a worker pid from the pool given the pool's load-balancing algorithm.
Like pick/1
, but returns the worker pid instead of the name.
pick_worker(Pool::any(), Value::any()) -> pid() | false
Pick a worker pid from the pool given the pool's load-balancing algorithm.
Like pick/2
, but returns the worker pid instead of the name.
ptest(N, I, Type, Opts) -> any()
randomize(Pool::any()) -> integer()
Randomizes the "next" pointer for the pool.
This function only has an effect for round_robin
pools, which have a
reference to the next worker to be picked. Without randomizing, the load
balancing will always start with the first worker in the pool.
remove_worker(Pool::any(), Name::any()) -> true
Remove a previously added worker.
This function will assume that any connected worker is disconnected first.
It will fail if there is no such pool, but will return true
in the case
when Name
did not exist in the pool in the first place.
setup_test_pool(P, Type0, Opts, Workers) -> any()
test_run0(N, X) -> any()
whereis_worker(Pool::any(), Name::any()) -> pid() | undefined
Look up the pid of a connected worker.
This function works similarly to gproc:where/1
: it will return the pid
of the worker connected as Pool / Name
, if there is such a worker; otherwise
it will return undefined
. It will raise an exception if Name
has not been
added to the pool.
worker_id(Pool, Name) -> GprocName
Return the unique gproc name corresponding to a name in the pool.
This function assumes that Name
has been added to Pool
. It returns the
unique name that a connected worker will be registered as. This doesn't mean
that there is, in fact, such a connected worker.
worker_pool(Pool::any()) -> [integer() | {Name, Pos}]
Return a list of slots and/or named workers in the pool.
This function is mainly for testing, but can also be useful when implementing your own worker placement algorithm on top of gproc_pool.
A plain integer represents an unfilled slot, and {Name, Pos}
represents an
added worker. The pool is always filled to the current size.