Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add command to send poolboy EXIT messages for unrelated pids; fix bugs

Sending poolboy EXIT messages for pids that are not workers causes
invalid changes to poolboy's state. Also, legitimate processes dying
after being checked back in also would corrupt the internal state. Add
better checking in the EXIT handling code to guard against this.
  • Loading branch information...
commit e964cc52e6dbda45d7fdcddf76836a2d5703b042 1 parent eacf28f
Andrew Thompson authored January 21, 2012
128  src/poolboy.erl
@@ -251,65 +251,75 @@ handle_info({'EXIT', Pid, _}, StateName, State) ->
251 251
            waiting = Waiting,
252 252
            max_overflow = MaxOverflow,
253 253
            worker_init = InitFun} = State,
254  
-    Monitors = case lists:keytake(Pid, 1, State#state.monitors) of
255  
-        {value, {_, Ref}, Left} -> erlang:demonitor(Ref), Left;
256  
-        false -> State#state.monitors
257  
-    end,
258  
-    case StateName of
259  
-        ready ->
260  
-            W = queue:filter(fun (P) -> P =/= Pid end, State#state.workers),
261  
-            {next_state, ready, State#state{workers=queue:in(new_worker(Sup, InitFun), W),
262  
-                                            monitors=Monitors}};
263  
-        overflow when Overflow == 0 ->
264  
-            W = queue:filter(fun (P) -> P =/= Pid end, State#state.workers),
265  
-            {next_state, ready, State#state{workers=queue:in(new_worker(Sup, InitFun), W),
266  
-                                            monitors=Monitors}};
267  
-        overflow ->
268  
-            {next_state, overflow, State#state{monitors=Monitors,
269  
-                                               overflow=Overflow-1}};
270  
-        full when MaxOverflow < 1 ->
271  
-            case queue:out(Waiting) of
272  
-              {{value, {{FromPid, _}=From, Timeout, StartTime}}, LeftWaiting} ->
273  
-                  case wait_valid(StartTime, Timeout) of
274  
-                      true ->
275  
-                          MonitorRef = erlang:monitor(process, FromPid),
276  
-                          Monitors2 = [{FromPid, MonitorRef} | Monitors],
277  
-                          gen_fsm:reply(From, new_worker(Sup, InitFun)),
278  
-                          {next_state, full, State#state{waiting=LeftWaiting,
279  
-                                                         monitors=Monitors2}};
280  
-                      _ ->
281  
-                          %% replay it
282  
-                          handle_info({'EXIT', Pid, foo}, StateName, State#state{waiting=LeftWaiting})
283  
-                  end;
284  
-              {empty, Empty} ->
285  
-                  Workers2 = queue:in(new_worker(Sup, InitFun), State#state.workers),
286  
-                  {next_state, ready, State#state{monitors=Monitors,
287  
-                                                  waiting=Empty,
288  
-                                                  workers=Workers2}}
289  
-          end;
290  
-        full when Overflow =< MaxOverflow ->
291  
-            case queue:out(Waiting) of
292  
-              {{value, {{FromPid, _}=From, Timeout, StartTime}}, LeftWaiting} ->
293  
-                  case wait_valid(StartTime, Timeout) of
294  
-                      true ->
295  
-                          MonitorRef = erlang:monitor(process, FromPid),
296  
-                          Monitors2 = [{FromPid, MonitorRef} | Monitors],
297  
-                          NewWorker = new_worker(Sup, InitFun),
298  
-                          gen_fsm:reply(From, NewWorker),
299  
-                          {next_state, full, State#state{waiting=LeftWaiting,
300  
-                                                         monitors=Monitors2}};
301  
-                      _ ->
302  
-                          %% replay it
303  
-                          handle_info({'EXIT', Pid, foo}, StateName, State#state{waiting=LeftWaiting})
304  
-                  end;
305  
-              {empty, Empty} ->
306  
-                  {next_state, overflow, State#state{monitors=Monitors,
307  
-                                                     overflow=Overflow-1,
308  
-                                                     waiting=Empty}}
309  
-          end;
310  
-        full ->
311  
-            {next_state, full, State#state{monitors=Monitors,
312  
-                                           overflow=Overflow-1}}
  254
+    case lists:keytake(Pid, 1, State#state.monitors) of
  255
+        {value, {_, Ref}, Monitors} -> erlang:demonitor(Ref),
  256
+            case StateName of
  257
+                ready ->
  258
+                    W = queue:filter(fun (P) -> P =/= Pid end, State#state.workers),
  259
+                    {next_state, ready, State#state{workers=queue:in(new_worker(Sup, InitFun), W),
  260
+                                                    monitors=Monitors}};
  261
+                overflow when Overflow == 0 ->
  262
+                    W = queue:filter(fun (P) -> P =/= Pid end, State#state.workers),
  263
+                    {next_state, ready, State#state{workers=queue:in(new_worker(Sup, InitFun), W),
  264
+                                                    monitors=Monitors}};
  265
+                overflow ->
  266
+                    {next_state, overflow, State#state{monitors=Monitors,
  267
+                                                       overflow=Overflow-1}};
  268
+                full when MaxOverflow < 1 ->
  269
+                    case queue:out(Waiting) of
  270
+                        {{value, {{FromPid, _}=From, Timeout, StartTime}}, LeftWaiting} ->
  271
+                            case wait_valid(StartTime, Timeout) of
  272
+                                true ->
  273
+                                    MonitorRef = erlang:monitor(process, FromPid),
  274
+                                    Monitors2 = [{FromPid, MonitorRef} | Monitors],
  275
+                                    gen_fsm:reply(From, new_worker(Sup, InitFun)),
  276
+                                    {next_state, full, State#state{waiting=LeftWaiting,
  277
+                                                                   monitors=Monitors2}};
  278
+                                _ ->
  279
+                                    %% replay it
  280
+                                    handle_info({'EXIT', Pid, foo}, StateName, State#state{waiting=LeftWaiting})
  281
+                            end;
  282
+                        {empty, Empty} ->
  283
+                            Workers2 = queue:in(new_worker(Sup, InitFun), State#state.workers),
  284
+                            {next_state, ready, State#state{monitors=Monitors,
  285
+                                                            waiting=Empty,
  286
+                                                            workers=Workers2}}
  287
+                    end;
  288
+                full when Overflow =< MaxOverflow ->
  289
+                    case queue:out(Waiting) of
  290
+                        {{value, {{FromPid, _}=From, Timeout, StartTime}}, LeftWaiting} ->
  291
+                            case wait_valid(StartTime, Timeout) of
  292
+                                true ->
  293
+                                    MonitorRef = erlang:monitor(process, FromPid),
  294
+                                    Monitors2 = [{FromPid, MonitorRef} | Monitors],
  295
+                                    NewWorker = new_worker(Sup, InitFun),
  296
+                                    gen_fsm:reply(From, NewWorker),
  297
+                                    {next_state, full, State#state{waiting=LeftWaiting,
  298
+                                                                   monitors=Monitors2}};
  299
+                                _ ->
  300
+                                    %% replay it
  301
+                                    handle_info({'EXIT', Pid, foo}, StateName, State#state{waiting=LeftWaiting})
  302
+                            end;
  303
+                        {empty, Empty} ->
  304
+                            {next_state, overflow, State#state{monitors=Monitors,
  305
+                                                               overflow=Overflow-1,
  306
+                                                               waiting=Empty}}
  307
+                    end;
  308
+                full ->
  309
+                    {next_state, full, State#state{monitors=Monitors,
  310
+                                                   overflow=Overflow-1}}
  311
+            end;
  312
+        _ ->
  313
+            %% not a monitored pid, is it in the worker queue?
  314
+            case queue:member(Pid, State#state.workers) of
  315
+                true ->
  316
+                    %% a checked in worker died
  317
+                    W = queue:filter(fun (P) -> P =/= Pid end, State#state.workers),
  318
+                    {next_state, StateName, State#state{workers=queue:in(new_worker(Sup, InitFun), W)}};
  319
+                _ ->
  320
+                    %% completely irrelevant pid exited, don't change anything
  321
+                    {next_state, StateName, State}
  322
+            end
313 323
     end;
314 324
 handle_info(_Info, StateName, State) ->
315 325
     {next_state, StateName, State}.
21  test/poolboy_eqc.erl
@@ -35,12 +35,25 @@ command(S) ->
35 35
 			[{call, ?MODULE, ?SHRINK(checkout_block, [checkout_nonblock]), [S#state.pid]} || S#state.pid /= undefined] ++
36 36
 			[{call, ?MODULE, checkin, [S#state.pid, elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
37 37
 			[{call, ?MODULE, kill_worker, [elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
38  
-			[{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined]
  38
+			[{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined] ++
  39
+			[{call, ?MODULE, spurious_exit, [S#state.pid]} || S#state.pid /= undefined]
39 40
 	).
40 41
 
41 42
 make_args(_S, Size, Overflow) ->
42 43
 	[[{size, Size}, {max_overflow, Overflow}, {worker_module, poolboy_test_worker}, {name, {local, poolboy_eqc}}]].
43 44
 
  45
+spawn_linked_process(Pool) ->
  46
+	Parent = self(),
  47
+	Pid = spawn(fun() ->
  48
+					link(Pool),
  49
+					Parent ! {linked, self()},
  50
+					timer:sleep(5000)
  51
+			end),
  52
+	receive
  53
+		{linked, Pid} ->
  54
+			Pid
  55
+	end.
  56
+
44 57
 start_poolboy(Args) ->
45 58
 	{ok, Pid} = poolboy:start_link(Args),
46 59
 	Pid.
@@ -75,6 +88,10 @@ kill_idle_worker(Pool) ->
75 88
 			kill_idle_worker(Pool)
76 89
 	end.
77 90
 
  91
+spurious_exit(Pool) ->
  92
+	Pid = spawn_linked_process(Pool),
  93
+	exit(Pid, kill).
  94
+
78 95
 precondition(S,{call,_,start_poolboy,_}) ->
79 96
 	%% only start new pool when old one is stopped
80 97
   S#state.pid == undefined;
@@ -161,6 +178,8 @@ next_state(S,_V,{call, _, checkin, [_Pool, Worker]}) ->
161 178
 next_state(S,_V,{call, _, kill_worker, [Worker]}) ->
162 179
 	S#state{checked_out=S#state.checked_out -- [Worker]};
163 180
 next_state(S,_V,{call, _, kill_idle_worker, [_Pool]}) ->
  181
+	S;
  182
+next_state(S,_V,{call, _, spurious_exit, [_Pool]}) ->
164 183
 	S.
165 184
 
166 185
 prop_sequential() ->

0 notes on commit e964cc5

Please sign in to comment.
Something went wrong with that request. Please try again.