YmerNode.Scripts.Throttle.Bucket (Ymer Node v0.2.1)

Copy Markdown View Source

A throttle's token bucket as a value: a rate per minute, a burst, the tokens it holds and the instant it last counted them.

Every function that needs the time takes it as an argument — a System.monotonic_time(:millisecond) instant, which YmerNode.Scripts.Throttle passes — so the arithmetic a throttle's waits and refusals rest on is proven here without a clock. There is no tick: a bucket is refilled from the elapsed time whenever it is read, so an idle throttle costs nothing between requests, and its tokens are a float, because a rate per minute refills by fractions.

Summary

Functions

Whether the bucket holds a whole token.

The bucket at now under new parameters: refilled at the rate it had until now, then held to the new burst. A throttle's parameters arrive with every request, so this is how a changed declaration takes effect without losing what the old one had counted.

A full bucket at now.

The bucket at now: what the elapsed time refilled, never more than the burst. An instant earlier than the last one counts no time at all.

The bucket with one token spent — only ever a bucket available?/1 answers true for.

How long, in milliseconds, until the bucket holds a token for a request with ahead requests waiting in front of it — 0 when it holds one now.

Types

t()

@type t() :: %YmerNode.Scripts.Throttle.Bucket{
  at: integer(),
  burst: pos_integer(),
  rate: pos_integer(),
  tokens: float()
}

Functions

available?(bucket)

Whether the bucket holds a whole token.

configure(bucket, rate, burst, now)

The bucket at now under new parameters: refilled at the rate it had until now, then held to the new burst. A throttle's parameters arrive with every request, so this is how a changed declaration takes effect without losing what the old one had counted.

Examples

iex> bucket = %YmerNode.Scripts.Throttle.Bucket{rate: 60, burst: 2, tokens: 2.0, at: 0}
iex> YmerNode.Scripts.Throttle.Bucket.configure(bucket, 30, 1, 0)
%YmerNode.Scripts.Throttle.Bucket{rate: 30, burst: 1, tokens: 1.0, at: 0}

new(rate, burst, now)

A full bucket at now.

Examples

iex> YmerNode.Scripts.Throttle.Bucket.new(60, 2, 0)
%YmerNode.Scripts.Throttle.Bucket{rate: 60, burst: 2, tokens: 2.0, at: 0}

refill(bucket, now)

The bucket at now: what the elapsed time refilled, never more than the burst. An instant earlier than the last one counts no time at all.

Examples

iex> bucket = %YmerNode.Scripts.Throttle.Bucket{rate: 60, burst: 2, tokens: 0.0, at: 0}
iex> YmerNode.Scripts.Throttle.Bucket.refill(bucket, 1_500).tokens
1.5
iex> YmerNode.Scripts.Throttle.Bucket.refill(bucket, 10_000).tokens
2.0

take(bucket)

The bucket with one token spent — only ever a bucket available?/1 answers true for.

Examples

iex> bucket = %YmerNode.Scripts.Throttle.Bucket{rate: 60, burst: 2, tokens: 1.5, at: 0}
iex> YmerNode.Scripts.Throttle.Bucket.take(bucket).tokens
0.5

wait(bucket, ahead)

How long, in milliseconds, until the bucket holds a token for a request with ahead requests waiting in front of it — 0 when it holds one now.

An estimate, and a conservative one: every request ahead is counted as taking its token and none as leaving. Rounded up, so a wait it names is never short.

Examples

iex> bucket = %YmerNode.Scripts.Throttle.Bucket{rate: 60, burst: 2, tokens: 1.5, at: 0}
iex> YmerNode.Scripts.Throttle.Bucket.wait(bucket, 0)
0
iex> YmerNode.Scripts.Throttle.Bucket.wait(bucket, 1)
500
iex> YmerNode.Scripts.Throttle.Bucket.wait(bucket, 100)
99500