Class: TrafficJam::SimpleLimit

Inherits:
Limit
  • Object
show all
Defined in:
lib/traffic_jam/simple_limit.rb

Overview

A SimpleLimit is a limit type that is more efficient for increments but does not support decrements or changing the max value without a complete reset. This means that if the period or max value for an action, value key changes, the used and remaining values cannot be preserved.

This works by storing a key in Redis with a millisecond-precision expiry representing the time that the limit will be completely reset. Each increment operation converts the increment amount into the number of milliseconds to be added to the expiry.

Example: Limit is 5 per 10 seconds.

An increment by 1 first sets the key to expire in 2s.
Another immediate increment by 4 sets the expiry to 10s.
Subsequent increments fail until clock time catches up to expiry

Instance Attribute Summary

Attributes inherited from Limit

#action, #max, #period, #value

Instance Method Summary collapse

Methods inherited from Limit

#exceeded?, #flatten, #increment!, #initialize, #limit_exceeded, #remaining, #reset

Constructor Details

This class inherits a constructor from TrafficJam::Limit

Instance Method Details

#decrement(_amount = 1, time: Time.now) ⇒ Object

Decrement the amount used by the given number.

Parameters:

  • amount (Integer)

    amount to decrement by

  • time (Time) (defaults to: Time.now)

    time is ignored

Raises:

  • (NotImplementedError)

    decrement is not defined for SimpleLimit



69
70
71
# File 'lib/traffic_jam/simple_limit.rb', line 69

def decrement(_amount = 1, time: Time.now)
  raise NotImplementedError, "decrement is not defined for SimpleLimit"
end

#increment(amount = 1, time: Time.now) ⇒ Boolean

Increment the amount used by the given number. Does not perform increment if the operation would exceed the limit. Returns whether the operation was successful.

Parameters:

  • amount (Integer) (defaults to: 1)

    amount to increment by

  • time (Time) (defaults to: Time.now)

    time is ignored

Returns:

  • (Boolean)

    true if increment succeded and false if incrementing would exceed the limit

Raises:

  • (ArgumentError)


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
# File 'lib/traffic_jam/simple_limit.rb', line 28

def increment(amount = 1, time: Time.now)
  return true if amount == 0
  return false if max == 0
  raise ArgumentError.new("Amount must be positive") if amount < 0

  if amount != amount.to_i
    raise ArgumentError.new("Amount must be an integer")
  end

  return false if amount > max

  incrby = (period * 1000 * amount / max).to_i
  argv = [incrby, period * 1000]

  result =
    begin
      redis.evalsha(
        Scripts::INCREMENT_SIMPLE_HASH, keys: [key], argv: argv)
    rescue Redis::CommandError
      redis.eval(Scripts::INCREMENT_SIMPLE, keys: [key], argv: argv)
    end

  case result
  when 0
    return true
  when -1
    raise Errors::InvalidKeyError, "Redis key #{key} has no expire time set"
  when -2
    return false
  else
    raise Errors::UnknownReturnValue,
          "Received unexpected return value #{result} from " \
          "increment_simple eval"
  end
end

#key_prefixObject



90
91
92
# File 'lib/traffic_jam/simple_limit.rb', line 90

def key_prefix
  "#{config.key_prefix}:s"
end

#usedInteger

Return amount of limit used, taking time drift into account.

Returns:

  • (Integer)

    amount used



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/traffic_jam/simple_limit.rb', line 76

def used
  return 0 if max.zero?

  expiry = redis.pttl(key)
  case expiry
  when -1  # key exists but has no associated expire
    raise Errors::InvalidKeyError, "Redis key #{key} has no expire time set"
  when -2  # key does not exist
    return 0
  end

  (max * expiry / (period * 1000.0)).ceil
end