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