1%%-------------------------------------------------------------------
2%%
3%% Copyright (c) 2016, James Fish <james@fishcakez.com>
4%%
5%% This file is provided to you under the Apache License,
6%% Version 2.0 (the "License"); you may not use this file
7%% except in compliance with the License. You may obtain
8%% a copy of the License at
9%%
10%% http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing,
13%% software distributed under the License is distributed on an
14%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15%% KIND, either express or implied. See the License for the
16%% specific language governing permissions and limitations
17%% under the License.
18%%
19%%-------------------------------------------------------------------
20-module(sregulator_rate_valve_statem).
21
22-include_lib("proper/include/proper.hrl").
23
24-export([module/0]).
25-export([args/0]).
26-export([init/3]).
27-export([handle_ask/2]).
28-export([handle_done/2]).
29-export([handle/2]).
30-export([timeout/2]).
31-export([config_change/4]).
32
33-record(state, {interval, intervals, overflow, active, time}).
34
35module() ->
36    sregulator_rate_valve.
37
38args() ->
39    ?LET({Limit, Interval, Min, Max}, gen_args(),
40         #{limit => Limit, interval => Interval, min => Min, max => Max}).
41
42gen_args() ->
43    ?LET({Min, Max},
44         ?SUCHTHAT({Min, Max}, {choose(0, 5), oneof([choose(0, 5), infinity])},
45                   Min =< Max),
46         {choose(0, 5), choose(1, 5), Min, Max}).
47
48init(#{limit := Limit, interval := Interval, min := Min, max := Max}, Size,
49     Time) ->
50    NInterval = erlang:convert_time_unit(Interval, milli_seconds, native),
51    case max(0, Size - (Min + Limit)) of
52        Overflow when Overflow > 0 ->
53            State = #state{interval=NInterval, intervals=[], active=Limit,
54                           overflow=Overflow},
55            {Min, Max, closed, State};
56        0 ->
57            Active = max(0, Size - Min),
58            Intervals = lists:duplicate(Limit-Active, 0),
59            State = #state{interval=NInterval, intervals=Intervals,
60                           active=Active, overflow=0, time=Time},
61            {Status, NState} = handle(Time, State),
62            {Min, Max, Status, NState}
63    end.
64
65handle_ask(Time, State) ->
66    {open, NState} = handle(Time, State),
67    #state{intervals=[_|Intervals], active=Active} = NState,
68    handle(State#state{intervals=Intervals, active=Active+1}).
69
70handle_done(Time, #state{overflow=Overflow} = State) when Overflow > 0 ->
71    handle(Time, State#state{overflow=Overflow-1});
72handle_done(Time, #state{overflow=0, active=Active} = State)
73  when Active > 0 ->
74    {_, #state{intervals=Intervals} = NState} = handle(Time, State),
75    handle(NState#state{active=Active-1, intervals=Intervals++[0]}).
76
77handle(Time, #state{intervals=Intervals, time=PrevTime} = State) ->
78    NIntervals = [Interval + (Time-PrevTime) || Interval <- Intervals],
79    handle(State#state{intervals=NIntervals, time=Time}).
80
81timeout(Time, State) ->
82    {_, NState} = handle(Time, State),
83    case NState of
84        #state{overflow=0, intervals=[Max | _], interval=Interval}
85          when Max < Interval ->
86            Time + (Interval - Max);
87        #state{} ->
88            infinity
89    end.
90
91config_change(#{limit := Limit, interval := Interval, min := Min, max := Max},
92              Size, Time, State) ->
93    {_, #state{intervals=Intervals}} = handle(Time, State),
94    NInterval = erlang:convert_time_unit(Interval, milli_seconds, native),
95    Active = max(0, Size - Min),
96    case Limit - Active of
97        NegOverflow when NegOverflow < 0 ->
98            NState = #state{interval=NInterval, intervals=[],
99                            overflow=-NegOverflow, active=Limit, time=Time},
100            {Min, Max, closed, NState};
101        Slots when length(Intervals) >= Slots ->
102            NIntervals = lists:sublist(Intervals, Slots),
103            NState = #state{interval=NInterval, intervals=NIntervals,
104                            active=Active, overflow=0, time=Time},
105            {Status, NState2} = handle(NState),
106            {Min, Max, Status, NState2};
107        Slots when length(Intervals) < Slots ->
108            New = lists:duplicate(Slots-length(Intervals), 0),
109            NIntervals = Intervals ++ New,
110            NState = #state{interval=NInterval, intervals=NIntervals,
111                            active=Active, overflow=0, time=Time},
112            {Status, NState2} = handle(NState),
113            {Min, Max, Status, NState2}
114    end.
115
116%% Internal
117
118handle(#state{overflow=0, interval=Interval, intervals=[Max|_]} = NState)
119  when Max >= Interval ->
120    {open, NState};
121handle(NState) ->
122    {closed, NState}.
123