Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Checkin non-worker processes 10% of the time and fix resulting bugs

Checking in an invalid process corrupts the internal state similarly to
receiving EXITs from non-worker pids.
  • Loading branch information...
commit e6af0b6a65cc8405e17b71626cfd81fe3311882f 1 parent e964cc5
Andrew Thompson Vagabond authored
Showing with 69 additions and 54 deletions.
  1. +61 −50 src/poolboy.erl
  2. +8 −4 test/poolboy_eqc.erl
111 src/poolboy.erl
View
@@ -77,12 +77,16 @@ init([], #state{size=Size, worker_sup=Sup, worker_init=InitFun,
{ok, StartState, State#state{workers=Workers}}.
ready({checkin, Pid}, State) ->
- Workers = queue:in(Pid, State#state.workers),
- Monitors = case lists:keytake(Pid, 1, State#state.monitors) of
- {value, {_, Ref}, Left} -> erlang:demonitor(Ref), Left;
- false -> State#state.monitors
- end,
- {next_state, ready, State#state{workers=Workers, monitors=Monitors}};
+ case lists:keytake(Pid, 1, State#state.monitors) of
+ {value, {_, Ref}, Monitors} ->
+ erlang:demonitor(Ref),
+ Workers = queue:in(Pid, State#state.workers),
+ {next_state, ready, State#state{workers=Workers,
+ monitors=Monitors}};
+ false ->
+ %% unknown process checked in, ignore it
+ {next_state, ready, State}
+ end;
ready(_Event, State) ->
{next_state, ready, State}.
@@ -122,27 +126,31 @@ ready(_Event, _From, State) ->
{reply, ok, ready, State}.
overflow({checkin, Pid}, #state{overflow=0}=State) ->
- %StopFun = State#state.worker_stop,
- %dismiss_worker(Pid, StopFun),
- Monitors = case lists:keytake(Pid, 1, State#state.monitors) of
- {value, {_, Ref}, Left} -> erlang:demonitor(Ref), Left;
- false -> []
- end,
- NextState = case State#state.size > 0 of
- true -> ready;
- _ -> overflow
- end,
- {next_state, NextState, State#state{overflow=0, monitors=Monitors,
- workers=queue:in(Pid, State#state.workers)}};
+ case lists:keytake(Pid, 1, State#state.monitors) of
+ {value, {_, Ref}, Monitors} ->
+ erlang:demonitor(Ref),
+ NextState = case State#state.size > 0 of
+ true -> ready;
+ _ -> overflow
+ end,
+ {next_state, NextState, State#state{overflow=0, monitors=Monitors,
+ workers=queue:in(Pid, State#state.workers)}};
+ false ->
+ %% unknown process checked in, ignore it
+ {next_state, overflow, State}
+ end;
overflow({checkin, Pid}, State) ->
#state{overflow=Overflow, worker_stop=StopFun} = State,
- dismiss_worker(Pid, StopFun),
- Monitors = case lists:keytake(Pid, 1, State#state.monitors) of
- {value, {_, Ref}, Left} -> erlang:demonitor(Ref), Left;
- false -> State#state.monitors
- end,
- {next_state, overflow, State#state{overflow=Overflow-1,
- monitors=Monitors}};
+ case lists:keytake(Pid, 1, State#state.monitors) of
+ {value, {_, Ref}, Monitors} ->
+ dismiss_worker(Pid, StopFun),
+ erlang:demonitor(Ref),
+ {next_state, overflow, State#state{overflow=Overflow-1,
+ monitors=Monitors}};
+ _ ->
+ %% unknown process checked in, ignore it
+ {next_state, overflow, State}
+ end;
overflow(_Event, State) ->
{next_state, overflow, State}.
@@ -174,32 +182,35 @@ overflow(_Event, _From, State) ->
full({checkin, Pid}, State) ->
#state{waiting = Waiting, max_overflow = MaxOverflow,
overflow = Overflow, worker_stop = StopFun} = State,
- Monitors = case lists:keytake(Pid, 1, State#state.monitors) of
- {value, {_, Ref0}, Left0} -> erlang:demonitor(Ref0), Left0;
- false -> State#state.monitors
- end,
- case queue:out(Waiting) of
- {{value, {{FromPid, _}=From, Timeout, StartTime}}, Left} ->
- case wait_valid(StartTime, Timeout) of
- true ->
- Ref = erlang:monitor(process, FromPid),
- Monitors1 = [{Pid, Ref} | Monitors],
- gen_fsm:reply(From, Pid),
- {next_state, full, State#state{waiting=Left,
- monitors=Monitors1}};
- _ ->
- %% replay this event with cleaned up waiting queue
- full({checkin, Pid}, State#state{waiting=Left})
+ case lists:keytake(Pid, 1, State#state.monitors) of
+ {value, {_, Ref0}, Monitors} ->
+ erlang:demonitor(Ref0),
+ case queue:out(Waiting) of
+ {{value, {{FromPid, _}=From, Timeout, StartTime}}, Left} ->
+ case wait_valid(StartTime, Timeout) of
+ true ->
+ Ref = erlang:monitor(process, FromPid),
+ Monitors1 = [{Pid, Ref} | Monitors],
+ gen_fsm:reply(From, Pid),
+ {next_state, full, State#state{waiting=Left,
+ monitors=Monitors1}};
+ _ ->
+ %% replay this event with cleaned up waiting queue
+ full({checkin, Pid}, State#state{waiting=Left})
+ end;
+ {empty, Empty} when MaxOverflow < 1 ->
+ Workers = queue:in(Pid, State#state.workers),
+ {next_state, ready, State#state{workers=Workers, waiting=Empty,
+ monitors=Monitors}};
+ {empty, Empty} ->
+ dismiss_worker(Pid, StopFun),
+ {next_state, overflow, State#state{waiting=Empty,
+ monitors=Monitors,
+ overflow=Overflow-1}}
end;
- {empty, Empty} when MaxOverflow < 1 ->
- Workers = queue:in(Pid, State#state.workers),
- {next_state, ready, State#state{workers=Workers, waiting=Empty,
- monitors=Monitors}};
- {empty, Empty} ->
- dismiss_worker(Pid, StopFun),
- {next_state, overflow, State#state{waiting=Empty,
- monitors=Monitors,
- overflow=Overflow-1}}
+ false ->
+ %% unknown process checked in, ignore it
+ {next_state, full, State}
end;
full(_Event, State) ->
{next_state, full, State}.
12 test/poolboy_eqc.erl
View
@@ -33,7 +33,7 @@ command(S) ->
[{call, ?MODULE, checkout_nonblock, [S#state.pid]} || S#state.pid /= undefined] ++
%% checkout shrinks to checkout_nonblock so we can simplify counterexamples
[{call, ?MODULE, ?SHRINK(checkout_block, [checkout_nonblock]), [S#state.pid]} || S#state.pid /= undefined] ++
- [{call, ?MODULE, checkin, [S#state.pid, elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
+ [{call, ?MODULE, checkin, [S#state.pid, fault({call, ?MODULE, spawn_process, []}, elements(S#state.checked_out))]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
[{call, ?MODULE, kill_worker, [elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
[{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined] ++
[{call, ?MODULE, spurious_exit, [S#state.pid]} || S#state.pid /= undefined]
@@ -42,6 +42,11 @@ command(S) ->
make_args(_S, Size, Overflow) ->
[[{size, Size}, {max_overflow, Overflow}, {worker_module, poolboy_test_worker}, {name, {local, poolboy_eqc}}]].
+spawn_process() ->
+ spawn(fun() ->
+ timer:sleep(5000)
+ end).
+
spawn_linked_process(Pool) ->
Parent = self(),
Pid = spawn(fun() ->
@@ -98,8 +103,6 @@ precondition(S,{call,_,start_poolboy,_}) ->
precondition(S,_) when S#state.pid == undefined ->
%% all other states need a running pool
false;
-precondition(S, {call, _, checkin, [_Pool, Pid]}) ->
- lists:member(Pid, S#state.checked_out);
precondition(S, {call, _, kill_worker, [Pid]}) ->
lists:member(Pid, S#state.checked_out);
precondition(S,{call,_,kill_idle_worker,[_Pool]}) ->
@@ -183,6 +186,7 @@ next_state(S,_V,{call, _, spurious_exit, [_Pool]}) ->
S.
prop_sequential() ->
+ fault_rate(1, 10,
?FORALL(Cmds,commands(?MODULE),
?TRAPEXIT(
aggregate(command_names(Cmds),
@@ -192,7 +196,7 @@ prop_sequential() ->
?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n~p\n",
[H,S,Res, zip(tl(Cmds), [Y || {_, Y} <- H])]),
Res == ok)
- end))).
+ end)))).
checkout_ok(S) ->
length(S#state.checked_out) < S#state.size + S#state.max_overflow.
Please sign in to comment.
Something went wrong with that request. Please try again.