Skip to content
supabasePublic
forked from devinus/poolboy

About

A hunky Erlang worker pool factory

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

 
 

Repository files navigation

Poolboy - A hunky Erlang worker pool factory

Poolboy is a lightweight, generic pooling library for Erlang with a focus on simplicity, performance, and rock-solid disaster recovery.

This is Supabase's fork of devinus/poolboy. It includes the following changes on top of upstream v1.5.2:

Idle overflow workers

Overflow workers are no longer killed immediately when checked back in. Instead, they stay available for a configurable period (default: 5 minutes) before being dismissed. This reduces churn under bursty workloads. Set the idle_timeout pool option (in milliseconds) to control how long idle overflow workers are kept around.

Hot code reload compatibility

Each pool now runs under a proper OTP supervision tree, making it compatible with hot code upgrades and standard OTP release tooling.

Breaking changes

  • poolboy:start/1,2 has been removed. Use poolboy:start_link/2.
  • Pools now require a name.
  • Requires OTP 21+.

Usage

1> Worker = poolboy:checkout(PoolName).
<0.9001.0>
2> gen_server:call(Worker, Request).
ok
3> poolboy:checkin(PoolName, Worker).
ok

Example

This is an example application showcasing database connection pools using Poolboy and epgsql.

example.app

{application, example, [
    {description, "An example application"},
    {vsn, "0.1"},
    {applications, [kernel, stdlib, sasl, crypto, ssl]},
    {modules, [example, example_worker]},
    {registered, [example]},
    {mod, {example, []}},
    {env, [
        {pools, [
            {pool1, [
                {size, 10},
                {max_overflow, 20}
			], [
                {hostname, "127.0.0.1"},
                {database, "db1"},
                {username, "db1"},
                {password, "abc123"}
            ]},
            {pool2, [
                {size, 5},
                {max_overflow, 10}
			], [
                {hostname, "127.0.0.1"},
                {database, "db2"},
                {username, "db2"},
                {password, "abc123"}
            ]}
        ]}
    ]}
]}.

example.erl

-module(example).
-behaviour(application).
-behaviour(supervisor).

-export([start/0, stop/0, squery/2, equery/3]).
-export([start/2, stop/1]).
-export([init/1]).

start() ->
    application:start(?MODULE).

stop() ->
    application:stop(?MODULE).

start(_Type, _Args) ->
    supervisor:start_link({local, example_sup}, ?MODULE, []).

stop(_State) ->
    ok.

init([]) ->
    {ok, Pools} = application:get_env(example, pools),
    PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) ->
        PoolArgs = [{name, {local, Name}},
            		{worker_module, example_worker}] ++ SizeArgs,
        poolboy:child_spec(Name, PoolArgs, WorkerArgs)
    end, Pools),
    {ok, {{one_for_one, 10, 10}, PoolSpecs}}.

squery(PoolName, Sql) ->
    poolboy:transaction(PoolName, fun(Worker) ->
        gen_server:call(Worker, {squery, Sql})
    end).

equery(PoolName, Stmt, Params) ->
    poolboy:transaction(PoolName, fun(Worker) ->
        gen_server:call(Worker, {equery, Stmt, Params})
    end).

example_worker.erl

-module(example_worker).
-behaviour(gen_server).
-behaviour(poolboy_worker).

-export([start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
         code_change/3]).

-record(state, {conn}).

start_link(Args) ->
    gen_server:start_link(?MODULE, Args, []).

init(Args) ->
    process_flag(trap_exit, true),
    Hostname = proplists:get_value(hostname, Args),
    Database = proplists:get_value(database, Args),
    Username = proplists:get_value(username, Args),
    Password = proplists:get_value(password, Args),
    {ok, Conn} = epgsql:connect(Hostname, Username, Password, [
        {database, Database}
    ]),
    {ok, #state{conn=Conn}}.

handle_call({squery, Sql}, _From, #state{conn=Conn}=State) ->
    {reply, epgsql:squery(Conn, Sql), State};
handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) ->
    {reply, epgsql:equery(Conn, Stmt, Params), State};
handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, #state{conn=Conn}) ->
    ok = epgsql:close(Conn),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

Options

  • name: the pool name
  • worker_module: the module that represents the workers
  • size: maximum pool size
  • max_overflow: maximum number of workers created if pool is empty
  • strategy: lifo or fifo, determines whether checked in workers should be placed first or last in the line of available workers. So, lifo operates like a traditional stack; fifo like a queue. Default is lifo.
  • idle_timeout: time in milliseconds to keep idle overflow workers alive before dismissing them. Default is 300000 (5 minutes). Can be changed at runtime with poolboy:set_idle_timeout/2, which also restarts the countdown for workers that are already idle.

Authors

License

Poolboy is available in the public domain (see UNLICENSE). Poolboy is also optionally available under the ISC license (see LICENSE), meant especially for jurisdictions that do not recognize public domain works.

About

A hunky Erlang worker pool factory

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages