Skip to content

Commit fb8705f

Browse files
committed
Update for OTP 22
1 parent b6218ae commit fb8705f

File tree

12 files changed

+288
-15
lines changed

12 files changed

+288
-15
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
.test
44
.eunit
55
.dialyzer_plt
6+
_build/*
7+
rebar.lock
8+
.DS_Store

Makefile

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
REBAR = ./rebar
2-
DIALYZER = dialyzer
1+
REBAR = ./rebar3
32

43
DIALYZER_WARNINGS = -Wunmatched_returns -Werror_handling \
54
-Wrace_conditions -Wunderspecs
@@ -12,7 +11,7 @@ compile:
1211
@$(REBAR) compile
1312

1413
test: compile
15-
@$(REBAR) eunit skip_deps=true
14+
@$(REBAR) do eunit skip_deps=true, cover
1615

1716
qc: compile
1817
@$(REBAR) qc skip_deps=true
@@ -23,10 +22,5 @@ clean:
2322
get-deps:
2423
@$(REBAR) get-deps
2524

26-
build-plt:
27-
@$(DIALYZER) --build_plt --output_plt .dialyzer_plt \
28-
--apps kernel stdlib
29-
3025
dialyze: compile
31-
@$(DIALYZER) --src src --plt .dialyzer_plt $(DIALYZER_WARNINGS) | \
32-
fgrep -vf .dialyzer-ignore-warnings
26+
@$(REBAR) dialyzer

rebar

-124 KB
Binary file not shown.

rebar.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
{platform_define, "^[0-9]+", namespaced_types}]}.
33
{eunit_opts, [verbose]}.
44
{cover_enabled, true}.
5+
{xref_checks,[undefined_function_calls,undefined_functions,locals_not_used]}.
6+
{profiles,
7+
[{test, [{erl_opts, [nowarn_export_all]}]}]}.

rebar3

679 KB
Binary file not shown.

src/poolboy.erl

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
-module(poolboy).
44
-behaviour(gen_fsm).
55

6+
-compile({nowarn_deprecated_function,
7+
[{gen_fsm, start_link, 3},
8+
{gen_fsm, start, 3},
9+
{gen_fsm, reply, 2},
10+
{gen_fsm, sync_send_event, 3},
11+
{gen_fsm, send_event, 2},
12+
{gen_fsm, sync_send_all_state_event, 2}]}).
13+
614
-export([checkout/1, checkout/2, checkout/3, checkin/2, transaction/2,
715
child_spec/2, child_spec/3, start/1, start/2, start_link/1,
816
start_link/2, stop/1, status/1]).
@@ -26,24 +34,24 @@
2634
-endif.
2735

2836
-record(state, {
29-
supervisor :: pid(),
30-
workers :: poolboy_queue(),
37+
supervisor :: pid() | undefined,
38+
workers :: poolboy_queue() |undefined,
3139
waiting :: poolboy_queue(),
3240
monitors :: ets:tid(),
3341
size = 5 :: non_neg_integer(),
3442
overflow = 0 :: non_neg_integer(),
3543
max_overflow = 10 :: non_neg_integer()
3644
}).
3745

38-
-spec checkout(Pool :: node()) -> pid().
46+
-spec checkout(Pool :: node() | pid()) -> pid().
3947
checkout(Pool) ->
4048
checkout(Pool, true).
4149

42-
-spec checkout(Pool :: node(), Block :: boolean()) -> pid() | full.
50+
-spec checkout(Pool :: node() | pid(), Block :: boolean()) -> pid() | full.
4351
checkout(Pool, Block) ->
4452
checkout(Pool, Block, ?TIMEOUT).
4553

46-
-spec checkout(Pool :: node(), Block :: boolean(), Timeout :: timeout())
54+
-spec checkout(Pool :: node() | pid(), Block :: boolean(), Timeout :: timeout())
4755
-> pid() | full.
4856
checkout(Pool, Block, Timeout) ->
4957
gen_fsm:sync_send_event(Pool, {checkout, Block, Timeout}, Timeout).
@@ -52,7 +60,7 @@ checkout(Pool, Block, Timeout) ->
5260
checkin(Pool, Worker) when is_pid(Worker) ->
5361
gen_fsm:send_event(Pool, {checkin, Worker}).
5462

55-
-spec transaction(Pool :: node(), Fun :: fun((Worker :: pid()) -> any()))
63+
-spec transaction(Pool :: node() | pid(), Fun :: fun((Worker :: pid()) -> any()))
5664
-> any().
5765
transaction(Pool, Fun) ->
5866
Worker = poolboy:checkout(Pool),

test/kill_race_ce.eqc

-455 Bytes
Binary file not shown.

test/poolboy_eqc.erl

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
-module(poolboy_eqc).
2+
-compile([poolboy_test_]). %%FIXME: Probably needs fix by adding missing functions.
3+
4+
-ifdef(TEST).
5+
-ifdef(EQC).
6+
-include_lib("eqc/include/eqc.hrl").
7+
-include_lib("eqc/include/eqc_statem.hrl").
8+
9+
-include_lib("eunit/include/eunit.hrl").
10+
11+
poolboy_test_() ->
12+
{timeout, 20,
13+
fun() ->
14+
?assert(eqc:quickcheck(eqc:testing_time(4,
15+
poolboy_eqc:prop_sequential()))),
16+
?assert(eqc:quickcheck(eqc:testing_time(4,
17+
poolboy_eqc:prop_parallel())))
18+
end
19+
}.
20+
21+
-record(state,
22+
{
23+
pid,
24+
size,
25+
max_overflow,
26+
checked_out = []
27+
}).
28+
29+
initial_state() ->
30+
#state{}.
31+
32+
command(S) ->
33+
oneof(
34+
[{call, ?MODULE, start_poolboy, make_args(S, nat(), nat())} || S#state.pid == undefined] ++
35+
[{call, ?MODULE, stop_poolboy, [S#state.pid]} || S#state.pid /= undefined] ++
36+
[{call, ?MODULE, checkout_nonblock, [S#state.pid]} || S#state.pid /= undefined] ++
37+
%% checkout shrinks to checkout_nonblock so we can simplify counterexamples
38+
[{call, ?MODULE, ?SHRINK(checkout_block, [checkout_nonblock]), [S#state.pid]} || S#state.pid /= undefined] ++
39+
[{call, ?MODULE, checkin, [S#state.pid, fault({call, ?MODULE, spawn_process, []}, elements(S#state.checked_out))]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
40+
[{call, ?MODULE, kill_worker, [elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
41+
[{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined] ++
42+
[{call, ?MODULE, spurious_exit, [S#state.pid]} || S#state.pid /= undefined]
43+
).
44+
45+
make_args(_S, Size, Overflow) ->
46+
[[{size, Size}, {max_overflow, Overflow}, {worker_module, poolboy_test_worker}, {name, {local, poolboy_eqc}}]].
47+
48+
spawn_process() ->
49+
{spawn(fun() ->
50+
timer:sleep(5000)
51+
end), self()}.
52+
53+
spawn_linked_process(Pool) ->
54+
Parent = self(),
55+
Pid = spawn(fun() ->
56+
link(Pool),
57+
Parent ! {linked, self()},
58+
timer:sleep(5000)
59+
end),
60+
receive
61+
{linked, Pid} ->
62+
Pid
63+
end.
64+
65+
start_poolboy(Args) ->
66+
{ok, Pid} = poolboy:start_link(Args),
67+
Pid.
68+
69+
stop_poolboy(Pid) ->
70+
gen_fsm:sync_send_all_state_event(Pid, stop),
71+
timer:sleep(1).
72+
73+
checkout_nonblock(Pool) ->
74+
{poolboy:checkout(Pool, false), self()}.
75+
76+
checkout_block(Pool) ->
77+
{catch(poolboy:checkout(Pool, true, 100)), self()}.
78+
79+
checkin(Pool, {Worker, _}) ->
80+
Res = poolboy:checkin(Pool, Worker),
81+
gen_fsm:sync_send_all_state_event(Pool, get_avail_workers),
82+
Res.
83+
84+
kill_worker({Worker, _}) ->
85+
exit(Worker, kill),
86+
timer:sleep(1),
87+
Worker.
88+
89+
kill_idle_worker(Pool) ->
90+
Pid = poolboy:checkout(Pool, false),
91+
case Pid of
92+
_ when is_pid(Pid) ->
93+
poolboy:checkin(Pool, Pid),
94+
kill_worker({Pid, self()});
95+
_ ->
96+
timer:sleep(1),
97+
kill_idle_worker(Pool)
98+
end.
99+
100+
spurious_exit(Pool) ->
101+
Pid = spawn_linked_process(Pool),
102+
exit(Pid, kill).
103+
104+
precondition(S,{call,_,start_poolboy,_}) ->
105+
%% only start new pool when old one is stopped
106+
S#state.pid == undefined;
107+
precondition(S,_) when S#state.pid == undefined ->
108+
%% all other states need a running pool
109+
false;
110+
precondition(S, {call, _, kill_worker, [Pid]}) ->
111+
lists:member(Pid, S#state.checked_out);
112+
precondition(S,{call,_,kill_idle_worker,[_Pool]}) ->
113+
length(S#state.checked_out) < S#state.size;
114+
precondition(S,{call,_,checkin,[_Pool, Pid]}) ->
115+
lists:member(Pid, S#state.checked_out);
116+
precondition(_S,{call,_,_,_}) ->
117+
true.
118+
119+
%% check model state against internal state, only used in sequential tests
120+
invariant(S = #state{pid=Pid},_) when Pid /= undefined ->
121+
State = if length(S#state.checked_out) == S#state.size + S#state.max_overflow ->
122+
full;
123+
length(S#state.checked_out) >= S#state.size ->
124+
overflow;
125+
true ->
126+
ready
127+
end,
128+
129+
Workers = max(0, S#state.size - length(S#state.checked_out)),
130+
OverFlow = max(0, length(S#state.checked_out) - S#state.size),
131+
Monitors = length(S#state.checked_out),
132+
133+
RealStatus = gen_fsm:sync_send_all_state_event(Pid, status),
134+
case RealStatus == {State, Workers, OverFlow, Monitors} of
135+
true ->
136+
true;
137+
_ ->
138+
{wrong_state, RealStatus, {State, Workers, OverFlow, Monitors}}
139+
end;
140+
invariant(_,_) ->
141+
true.
142+
143+
%% what states block
144+
blocking(S, {call, _, checkout_block, _}) ->
145+
%% blocking checkout can block if we expect a checkout to fail
146+
not checkout_ok(S);
147+
blocking(_, _) ->
148+
false.
149+
150+
postcondition(S,{call,_,checkout_block,[_Pool]},R) ->
151+
case R of
152+
{{'EXIT', {timeout, _}}, _} ->
153+
case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of
154+
true ->
155+
true;
156+
_ ->
157+
{checkout_block, R}
158+
end;
159+
_ ->
160+
case length(S#state.checked_out) < S#state.size + S#state.max_overflow of
161+
true ->
162+
true;
163+
_ ->
164+
{checkout_block, R}
165+
end
166+
end;
167+
postcondition(S,{call,_,checkout_nonblock,[_Pool]},R) ->
168+
case R of
169+
{full, _} ->
170+
case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of
171+
true ->
172+
true;
173+
_ ->
174+
{checkout_nonblock, R}
175+
end;
176+
_ ->
177+
case length(S#state.checked_out) < S#state.size + S#state.max_overflow of
178+
true ->
179+
true;
180+
_ ->
181+
{checkout_block, R}
182+
end
183+
end;
184+
postcondition(_S, {call,_,checkin,_}, R) ->
185+
case R of
186+
ok ->
187+
true;
188+
_ ->
189+
{checkin, R}
190+
end;
191+
postcondition(_S,{call,_,_,_},_R) ->
192+
true.
193+
194+
next_state(S,V,{call,_,start_poolboy, [Args]}) ->
195+
S#state{pid=V,
196+
size=proplists:get_value(size, Args),
197+
max_overflow=proplists:get_value(max_overflow, Args)
198+
};
199+
next_state(S,_V,{call,_,stop_poolboy, [_Args]}) ->
200+
S#state{pid=undefined, checked_out=[]};
201+
next_state(S,V,{call,_,checkout_block,_}) ->
202+
%% if the model says the checkout worked, store the result
203+
case checkout_ok(S) of
204+
false ->
205+
S;
206+
_ ->
207+
S#state{checked_out=S#state.checked_out++[V]}
208+
end;
209+
next_state(S,V,{call,_,checkout_nonblock,_}) ->
210+
%% if the model says the checkout worked, store the result
211+
case checkout_ok(S) of
212+
false ->
213+
S;
214+
_ ->
215+
S#state{checked_out=S#state.checked_out++[V]}
216+
end;
217+
next_state(S,_V,{call, _, checkin, [_Pool, Worker]}) ->
218+
S#state{checked_out=S#state.checked_out -- [Worker]};
219+
next_state(S,_V,{call, _, kill_worker, [Worker]}) ->
220+
S#state{checked_out=S#state.checked_out -- [Worker]};
221+
next_state(S,_V,{call, _, kill_idle_worker, [_Pool]}) ->
222+
S;
223+
next_state(S,_V,{call, _, spurious_exit, [_Pool]}) ->
224+
S;
225+
next_state(S,V,{call, erlang, self, []}) ->
226+
%% added after test generation, values are never symbolic
227+
S#state{checked_out=[{Worker, Pid} || {Worker, Pid} <- S#state.checked_out, Pid /= V]}.
228+
229+
230+
prop_sequential() ->
231+
fault_rate(1, 10,
232+
?FORALL(Cmds,commands(?MODULE),
233+
?TRAPEXIT(
234+
aggregate(command_names(Cmds),
235+
begin
236+
{H,S,Res} = run_commands(?MODULE,Cmds),
237+
catch(stop_poolboy(whereis(poolboy_eqc))),
238+
?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n~p\n",
239+
[H,S,Res, zip(Cmds, [Y || {_, Y} <- H])]),
240+
Res == ok)
241+
end)))).
242+
243+
prop_parallel() ->
244+
fault_rate(1, 10,
245+
?FORALL(Cmds={Seq,Par},parallel_commands(?MODULE),
246+
?TRAPEXIT(
247+
aggregate(command_names(Cmds),
248+
begin
249+
NewPar = [P ++ [{set, {var, 0}, {call, erlang, self, []}}] || P <- Par],
250+
{H,S,Res} = run_parallel_commands(?MODULE,{Seq,NewPar}),
251+
catch(stop_poolboy(whereis(poolboy_eqc))),
252+
?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n",
253+
[H,S,Res]),
254+
Res == ok)
255+
end)))).
256+
257+
258+
checkout_ok(S) ->
259+
length(S#state.checked_out) < S#state.size + S#state.max_overflow.
260+
261+
-endif.
262+
-endif.

test/poolboy_tests.erl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
-include_lib("eunit/include/eunit.hrl").
44

5+
-compile({nowarn_deprecated_function,
6+
[{gen_fsm, sync_send_all_state_event, 2}]}).
7+
58
-define(sync(Pid, Event),
69
gen_fsm:sync_send_all_state_event(Pid, Event)).
710

test/unknown2_ce.eqc

-1.47 KB
Binary file not shown.

0 commit comments

Comments
 (0)