diff --git a/README.md b/README.md index 495d812..1a6efff 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,7 @@ ok ## Example -This is an example application showcasing database connection pools using -Poolboy and [epgsql](https://github.com/epgsql/epgsql). +This is a very simple Poolboy example showcasing the call flow. Print statements at various checkpoints and dummy call of workers (doing nothing actually) is what we aim to demonstrate in this example. ### example.app @@ -38,20 +37,20 @@ Poolboy and [epgsql](https://github.com/epgsql/epgsql). {pool1, [ {size, 10}, {max_overflow, 20} - ], [ - {hostname, "127.0.0.1"}, - {database, "db1"}, - {username, "db1"}, - {password, "abc123"} + ], [ + {hostname, "127.0.0.1/login.php"}, + {database, "database"}, + {username, "root"}, + {password, "rootpassword"} ]}, {pool2, [ {size, 5}, {max_overflow, 10} - ], [ - {hostname, "127.0.0.1"}, - {database, "db2"}, - {username, "db2"}, - {password, "abc123"} + ], [ + {hostname, "127.0.0.1/login.php"}, + {database, "database"}, + {username, "root"}, + {password, "rootpassword"} ]} ]} ]} @@ -65,40 +64,55 @@ Poolboy and [epgsql](https://github.com/epgsql/epgsql). -behaviour(application). -behaviour(supervisor). --export([start/0, stop/0, squery/2, equery/3]). +-export([start/0, stop/0, squery/2, equery/3, xquery/3]). -export([start/2, stop/1]). -export([init/1]). start() -> + io:fwrite("#MESSAGE : ~p calls example:start()~n", [self()]), application:start(?MODULE). stop() -> + io:fwrite("#MESSAGE : ~p calls example:stop()~n", [self()]), application:stop(?MODULE). start(_Type, _Args) -> + io:fwrite("#MESSAGE : ~p calls supervisor:start_link()~n(default callback is example:init())~n", [self()]), supervisor:start_link({local, example_sup}, ?MODULE, []). stop(_State) -> ok. init([]) -> + io:fwrite("#MESSAGE : ~p called example:init()~n", [self()]), {ok, Pools} = application:get_env(example, pools), + io:fwrite("#MESSAGE : ~p defining PoolSpecs~n", [self()]), PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) -> + io:fwrite("#MESSAGE : ~p defining PoolArgs~n", [self()]), PoolArgs = [{name, {local, Name}}, {worker_module, example_worker}] ++ SizeArgs, poolboy:child_spec(Name, PoolArgs, WorkerArgs) end, Pools), + io:fwrite("#MESSAGE : ~p example:init() call is now complete~n", [self()]), {ok, {{one_for_one, 10, 10}, PoolSpecs}}. squery(PoolName, Sql) -> + io:fwrite("i called squery call~n"), poolboy:transaction(PoolName, fun(Worker) -> gen_server:call(Worker, {squery, Sql}) end). equery(PoolName, Stmt, Params) -> + io:fwrite("i called equery call~n"), poolboy:transaction(PoolName, fun(Worker) -> gen_server:call(Worker, {equery, Stmt, Params}) end). + +xquery(PoolName, Stmt, Params) -> + io:fwrite("i called xquery cast~n"), + poolboy:transaction(PoolName, fun(Worker) -> + gen_server:cast(Worker, {xquery, Stmt, Params}) + end). ``` ### example_worker.erl @@ -111,6 +125,7 @@ equery(PoolName, Stmt, Params) -> -export([start_link/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([warmup /0]). -record(state, {conn}). @@ -118,36 +133,61 @@ start_link(Args) -> gen_server:start_link(?MODULE, Args, []). init(Args) -> + io:fwrite("#MESSAGE : ~p defines a new worker~n", [self()]), 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}}. + io:fwrite("#MESSAGE : ~p will be starting to warmup~n", [self()]), + {ok} = warmup(), + io:fwrite("#MESSAGE : ~p warmup is complete~n~n~n", [self()]), + {ok, ok}. + +warmup() -> + io:fwrite("#MESSAGE : ~p is now warming up~n", [self()]), + {ok}. handle_call({squery, Sql}, _From, #state{conn=Conn}=State) -> + io:fwrite("lets handle a squery call~n"), {reply, epgsql:squery(Conn, Sql), State}; handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) -> + io:fwrite("lets handle a equery call~n"), {reply, epgsql:equery(Conn, Stmt, Params), State}; handle_call(_Request, _From, State) -> + io:fwrite("call handling success~n"), {reply, ok, State}. handle_cast(_Msg, State) -> + io:fwrite("lets handle a cast (async call)~n"), {noreply, State}. handle_info(_Info, State) -> + io:fwrite("lets example_worker:handle_info~n"), {noreply, State}. -terminate(_Reason, #state{conn=Conn}) -> - ok = epgsql:close(Conn), +terminate(_Reason, ok) -> + io:fwrite("#MESSAGE : ~p wants to terminate the worker~n", [self()]), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. ``` +## Example usage + +```erl-sh +1> example:start(). +--- % start the supervisors, initiate the workers +2> example:squery(pool1, hello). +--- % get the workers to work : (synchronous) call +3> example:equery(pool1, hello, world). +--- % get the workers to work : (synchronous) call +4> example:xquery(pool1, hello, world). +--- % get the workers to work : (asynchronous) cast +5> example:stop(). +--- % kill the workers and end the pool +``` +Find the complete terminal screenshots along with stats and explanation [here](https://github.com/rounakdatta/poolboy/edit/master/report.pdf). ## Options diff --git a/example/example.app b/example/example.app new file mode 100644 index 0000000..469aec0 --- /dev/null +++ b/example/example.app @@ -0,0 +1,30 @@ +{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"} + ]} + ]} + ]} +]}. \ No newline at end of file diff --git a/example/example.erl b/example/example.erl new file mode 100644 index 0000000..2492af8 --- /dev/null +++ b/example/example.erl @@ -0,0 +1,53 @@ +-module(example). +-behaviour(application). +-behaviour(supervisor). + +-export([start/0, stop/0, squery/2, equery/3, xquery/3]). +-export([start/2, stop/1]). +-export([init/1]). + +start() -> + io:fwrite("#MESSAGE : ~p calls example:start()~n", [self()]), + application:start(?MODULE). + +stop() -> + io:fwrite("#MESSAGE : ~p calls example:stop()~n", [self()]), + application:stop(?MODULE). + +start(_Type, _Args) -> + io:fwrite("#MESSAGE : ~p calls supervisor:start_link()~n(default callback is example:init())~n", [self()]), + supervisor:start_link({local, example_sup}, ?MODULE, []). + +stop(_State) -> + ok. + +init([]) -> + io:fwrite("#MESSAGE : ~p called example:init()~n", [self()]), + {ok, Pools} = application:get_env(example, pools), + io:fwrite("#MESSAGE : ~p defining PoolSpecs~n", [self()]), + PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) -> + io:fwrite("#MESSAGE : ~p defining PoolArgs~n", [self()]), + PoolArgs = [{name, {local, Name}}, + {worker_module, example_worker}] ++ SizeArgs, + poolboy:child_spec(Name, PoolArgs, WorkerArgs) + end, Pools), + io:fwrite("#MESSAGE : ~p example:init() call is now complete~n", [self()]), + {ok, {{one_for_one, 10, 10}, PoolSpecs}}. + +squery(PoolName, Sql) -> + io:fwrite("i called squery call~n"), + poolboy:transaction(PoolName, fun(Worker) -> + gen_server:call(Worker, {squery, Sql}) + end). + +equery(PoolName, Stmt, Params) -> + io:fwrite("i called equery call~n"), + poolboy:transaction(PoolName, fun(Worker) -> + gen_server:call(Worker, {equery, Stmt, Params}) + end). + +xquery(PoolName, Stmt, Params) -> + io:fwrite("i called xquery cast~n"), + poolboy:transaction(PoolName, fun(Worker) -> + gen_server:cast(Worker, {xquery, Stmt, Params}) + end). \ No newline at end of file diff --git a/example/example_worker.erl b/example/example_worker.erl new file mode 100644 index 0000000..0b333ca --- /dev/null +++ b/example/example_worker.erl @@ -0,0 +1,55 @@ +-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]). +-export([warmup /0]). + +-record(state, {conn}). + +start_link(Args) -> + %io:fwrite("#MESSAGE : ~p calls example_worker:start_link()~n(default callback is example_worker:init())~n"), + gen_server:start_link(?MODULE, Args, []). + +init(Args) -> + io:fwrite("#MESSAGE : ~p defines a new worker~n", [self()]), + 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), + io:fwrite("#MESSAGE : ~p will be starting to warmup~n", [self()]), + {ok} = warmup(), + io:fwrite("#MESSAGE : ~p warmup is complete~n~n~n", [self()]), + {ok, ok}. + +warmup() -> + io:fwrite("#MESSAGE : ~p is now warming up~n", [self()]), + {ok}. + +handle_call({squery, Sql}, _From, #state{conn=Conn}=State) -> + io:fwrite("lets handle a squery call~n"), + {reply, epgsql:squery(Conn, Sql), State}; +handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) -> + io:fwrite("lets handle a equery call~n"), + {reply, epgsql:equery(Conn, Stmt, Params), State}; +handle_call(_Request, _From, State) -> + io:fwrite("call handling success~n"), + {reply, ok, State}. + +handle_cast(_Msg, State) -> + io:fwrite("lets handle a cast (async call)~n"), + {noreply, State}. + +handle_info(_Info, State) -> + io:fwrite("lets example_worker:handle_info~n"), + {noreply, State}. + +terminate(_Reason, ok) -> + io:fwrite("#MESSAGE : ~p wants to terminate the worker~n", [self()]), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. \ No newline at end of file diff --git a/report.pdf b/report.pdf new file mode 100644 index 0000000..6f984e1 Binary files /dev/null and b/report.pdf differ diff --git a/src/poolboy.erl b/src/poolboy.erl index db4973b..bb1decd 100644 --- a/src/poolboy.erl +++ b/src/poolboy.erl @@ -124,6 +124,7 @@ status(Pool) -> init({PoolArgs, WorkerArgs}) -> process_flag(trap_exit, true), + io:fwrite("#MESSAGE : ~p starting poolboy server~n", [self()]), Waiting = queue:new(), Monitors = ets:new(monitors, [private]), init(PoolArgs, WorkerArgs, #state{waiting = Waiting, monitors = Monitors}). diff --git a/src/poolboy_sup.erl b/src/poolboy_sup.erl index e6485a6..b85e128 100644 --- a/src/poolboy_sup.erl +++ b/src/poolboy_sup.erl @@ -9,6 +9,7 @@ start_link(Mod, Args) -> supervisor:start_link(?MODULE, {Mod, Args}). init({Mod, Args}) -> + io:fwrite("#MESSAGE : ~p starting poolboy supervisor~n", [self()]), {ok, {{simple_one_for_one, 0, 1}, [{Mod, {Mod, start_link, [Args]}, temporary, 5000, worker, [Mod]}]}}.