View Source speed_trap_token_bucket (speed_trap v1.1.1)
speed_trap_token_bucket implementation with atomics.
The current number of tokens is stored as a mutable atomic variable. When a token is taken from the bucket, the counter is decremented. It is allowed for the counter to become negative.
Adding tokens to the counter requires multiple operations. Since there is no operation to add with a threshold (contrary to ets:update_counter/3 for example), the value is first read and then incremented if necessary. It is possible that the value will be decremented between the two operations, but it cannot be incremented (given there's only one updater running at a time), so there's no risk of adding tokens above the bucket's capacity. On the other hand, the counter going below 0 is an issue that needs special handling: whenever the update operation finds a negative value, it sets the counter to 1. Incrementing it is not safe in this case, as concurrent processes may try to get tokens from the bucket, pushing its value to even lower between the read and the update.
It's worth mentioning that although counters can underflow, it is not a practical risk to call get_token/1 2^63 times in a single refill interval.
timer:apply_interval/4, however an interval timer is linked to the process creating it. Therefore we need a simple server to own and manage these timers.
Summary
Functions
Types
-type state() :: #{speed_trap:id() => timer:tref()}.
-type token_bucket() :: atomics:atomics_ref().
Functions
-spec active_buckets() -> [{speed_trap:id(), speed_trap:stored_options()}].
-spec add_token(token_bucket(), speed_trap:bucket_size(), speed_trap:refill_count(), boolean()) -> ok.
-spec bucket(speed_trap:id()) -> {ok, {speed_trap:stored_options(), token_bucket()}} | {error, speed_trap:no_such_speed_trap()}.
-spec delete(speed_trap:id(), boolean()) -> ok | {error, speed_trap:no_such_speed_trap()}.
-spec delete_override(speed_trap:id()) -> ok.
-spec delete_overrides() -> ok.
-spec get_override(speed_trap:id()) -> {ok, speed_trap:modify_options()} | {error, not_found}.
-spec get_token(speed_trap:id()) -> {ok, speed_trap:try_pass_success()} | {error, speed_trap:no_such_speed_trap() | speed_trap:too_many_requests() | speed_trap:blocked()}.
-spec init([]) -> {ok, state()}.
-spec modify(speed_trap:id(), speed_trap:modify_options()) -> ok | {error, speed_trap_options:bad_options() | speed_trap:no_such_speed_trap()}.
-spec new(speed_trap:id(), speed_trap:options()) -> ok | {error, speed_trap:already_exists()}.
-spec options(speed_trap:id()) -> {ok, speed_trap:stored_options()} | {error, speed_trap:no_such_speed_trap()}.
-spec return_token(speed_trap:id()) -> ok | {error, speed_trap:no_such_speed_trap()}.