Skip to content

Commit

Permalink
Replace timer with monotonic time in gproc_pool (#195)
Browse files Browse the repository at this point in the history
I have often observed stray timeout messages even after modifying
clear_wait/1 to flush out these messages like so:

clear_wait(nowait) ->
    ok;
clear_wait({busy_wait, Ref}) ->
    erlang:cancel_timer(Ref),
    receive {timeout, Ref, _} -> ok
    after 0 -> ok
    end.

The documentation of erlang:cancel_timer/2 has this to say:

  ...Even if the timer had expired, it does not tell you if the time-out
  message has arrived at its destination yet.

That means getting rid of unwanted timeout messages is racy and
expensive. In this change monotonic time is used to detect timeout
which avoids this problem.
  • Loading branch information
leoliu authored Nov 7, 2023
1 parent 4ca45e0 commit 2ac257f
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 22 deletions.
Empty file modified src/gproc_lib.erl
100755 → 100644
Empty file.
30 changes: 8 additions & 22 deletions src/gproc_pool.erl
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,7 @@ claim(Pool, F, Wait) ->
case gproc:get_value(?POOL(Pool), shared) of
{0, _} -> false;
{_, claim} ->
W = setup_wait(Wait, Pool),
claim_w(Pool, F, W);
claim_w(Pool, F, setup_wait(Wait));
_ ->
error(badarg)
end.
Expand All @@ -452,7 +451,6 @@ claim_w(Pool, F, W) ->
false ->
claim_w(Pool, F, do_wait(W));
Other ->
clear_wait(W),
Other
end.

Expand Down Expand Up @@ -554,35 +552,23 @@ execute_claim(F, K, Pid) ->
?MAY_FAIL(gproc:reset_counter(K))
end.

setup_wait(nowait, _) ->
setup_wait(nowait) ->
nowait;
setup_wait({busy_wait, MS}, Pool) ->
Ref = erlang:start_timer(MS, self(), {claim, Pool}),
{busy_wait, Ref}.
setup_wait({busy_wait, MS}) ->
{busy_wait, erlang:monotonic_time(millisecond) + MS}.

do_wait(nowait) ->
timeout;
do_wait({busy_wait, Ref} = W) ->
do_wait({busy_wait, EndTime} = W) ->
%% Yielding here serves two purposes:
%% 1) Increase the chance that whoever's before us can finish
%% 2) The value of read_timer/1 only refreshes after yield (so I've heard)
erlang:yield(),
case erlang:read_timer(Ref) of
false ->
receive {timeout, Ref, _} -> ok
after 0 -> ok
end,
timeout;
_ ->
W
case erlang:monotonic_time(millisecond) >= EndTime of
true -> timeout;
false -> W
end.

clear_wait(nowait) ->
ok;
clear_wait({busy_wait, Ref}) ->
erlang:cancel_timer(Ref),
ok.

%% @spec log(GprocKey) -> integer()
%% @doc Update a counter associated with a worker name.
%%
Expand Down

0 comments on commit 2ac257f

Please sign in to comment.