1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
-module(loop).
-export([timeout/2]).
-export([idle/2, waiting/2]).
-type request_category() :: category1 | category2.
-type counter() :: counter1 | 2.
-type counters() :: #{counter() => non_neg_integer()}.
-record(queue, {limit = 0 :: non_neg_integer(),
buffer = [] :: list()}).
-type request_queues() :: #{request_category() => #queue{}}.
-record(?MODULE,
{state = idle :: idle | waiting,
timer = undefined :: undefined | timer:tref(),
queues = #{category1 => #queue{},
category2 => #queue{}} :: request_queues(),
counters = new_counters() :: counters()}).
-spec timeout(Ref, Timer :: timer:tref()) -> {noreply, Ref}.
timeout(Ref, Timer) ->
handle_message(Ref, {timeout, Timer}).
-type message() :: {reset, request_category()}
| {timeout, timer:tref()}.
-spec handle_message(Ref, Message :: message()) ->
{reply, boolean(), Ref} | {noreply, Ref}.
handle_message(Ref, Msg) ->
MV = #?MODULE{state = State} = get(mv),
case apply(?MODULE, State, [Msg, MV]) of
{reply, Result, NewMV} ->
put(mv, NewMV),
{reply, Result, Ref};
{noreply, NewMV} ->
put(mv, NewMV),
{noreply, Ref}
end.
-spec idle(Message :: message(), #?MODULE{}) ->
{reply, boolean(), #?MODULE{}} | {noreply, #?MODULE{}}.
idle({reset, Category}, MV = #?MODULE{queues = Queues}) ->
case Queues of
#{Category := #queue{limit = 0}} ->
{reply, false, MV};
_ ->
wait(MV)
end;
idle(_, MV) ->
{noreply, MV}.
-spec waiting(Message :: message(), #?MODULE{}) ->
{reply, boolean(), #?MODULE{}} | {noreply, #?MODULE{}}.
waiting({reset, _Category}, MV = #?MODULE{}) ->
NewMV = stop_timer(MV),
{noreply, NewMV#?MODULE{state = idle}};
waiting({timeout, Timer}, #?MODULE{timer = Timer} = MV) ->
%% The opaque warning is an effect of the call to timer:send_after().
{noreply, start_timer(MV#?MODULE{timer = undefined,
counters = new_counters()})}.
-spec wait(#?MODULE{}) -> {noreply, #?MODULE{}}.
wait(MV) ->
{noreply, start_timer(MV#?MODULE{state = waiting})}.
-spec stop_timer(#?MODULE{}) -> #?MODULE{}.
stop_timer(MV) ->
case MV#?MODULE.timer of
undefined ->
MV;
Timer ->
timer:cancel(Timer),
MV#?MODULE{timer = undefined}
end.
-spec start_timer(MV :: #?MODULE{}) -> #?MODULE{}.
start_timer(MV) ->
case MV#?MODULE.timer of
undefined ->
%% Note: timer:send_after() returns {ok, TRef} | {error, _}.
MV#?MODULE{timer = timer:send_after(1000, ?MODULE)};
_Timer ->
start_timer(stop_timer(MV))
end.
-spec new_counters() -> counters().
new_counters() ->
#{counter1 => 10, 2 => 10}.
|