Class: Pecorino::CachedThrottle

Inherits:
Object
  • Object
show all
Defined in:
lib/pecorino/cached_throttle.rb

Overview

The cached throttles can be used when you want to lift your throttle blocks into a higher-level cache. If you are dealing with clients which are hammering on your throttles a lot, it is useful to have a process-local cache of the timestamp when the blocks that are set are going to expire. If you are running, say, 10 web app containers - and someone is hammering at an endpoint which starts blocking - you don’t really need to query your DB for every request. The first request indicated as “blocked” by Pecorino can write a cache entry into a shared in-memory table, and all subsequent calls to the same process can reuse that ‘blocked_until` value to quickly refuse the request

Instance Method Summary collapse

Constructor Details

#initialize(cache_store, throttle) ⇒ CachedThrottle

Returns a new instance of CachedThrottle.

Parameters:

  • cache_store (ActiveSupport::Cache::Store)

    the store for the cached blocks. We recommend a MemoryStore per-process.

  • throttle (Pecorino::Throttle)

    the throttle to cache



13
14
15
16
# File 'lib/pecorino/cached_throttle.rb', line 13

def initialize(cache_store, throttle)
  @cache_store = cache_store
  @throttle = throttle
end

Instance Method Details

#able_to_accept?(n = 1) ⇒ Boolean

Returns ‘false` if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

Returns:

  • (Boolean)

See Also:



46
47
48
49
50
51
# File 'lib/pecorino/cached_throttle.rb', line 46

def able_to_accept?(n = 1)
  blocked_state = read_cached_blocked_state
  return false if blocked_state&.blocked?

  @throttle.able_to_accept?(n)
end

#keyObject

Returns the key of the throttle

See Also:



65
66
67
# File 'lib/pecorino/cached_throttle.rb', line 65

def key
  @throttle.key
end

#request(n = 1) ⇒ Object

Returns cached ‘state` for the throttle if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

See Also:



34
35
36
37
38
39
40
41
# File 'lib/pecorino/cached_throttle.rb', line 34

def request(n = 1)
  blocked_state = read_cached_blocked_state
  return blocked_state if blocked_state&.blocked?

  @throttle.request(n).tap do |state|
    write_cache_blocked_state(state) if state.blocked_until
  end
end

#request!(n = 1) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
# File 'lib/pecorino/cached_throttle.rb', line 19

def request!(n = 1)
  blocked_state = read_cached_blocked_state
  raise Pecorino::Throttle::Throttled.new(@throttle, blocked_state) if blocked_state&.blocked?

  begin
    @throttle.request!(n)
  rescue Pecorino::Throttle::Throttled => throttled_ex
    write_cache_blocked_state(throttled_ex.state) if throttled_ex.throttle == @throttle
    raise
  end
end

#stateObject

Returns ‘false` if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.



72
73
74
75
76
77
78
79
80
# File 'lib/pecorino/cached_throttle.rb', line 72

def state
  blocked_state = read_cached_blocked_state
  warn "Read blocked state #{blocked_state.inspect}"
  return blocked_state if blocked_state&.blocked?

  @throttle.state.tap do |state|
    write_cache_blocked_state(state) if state.blocked?
  end
end

#throttled(&blk) ⇒ Object

Does not run the block if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

See Also:



56
57
58
59
60
# File 'lib/pecorino/cached_throttle.rb', line 56

def throttled(&blk)
  # We can't wrap the implementation of "throttled". Or - we can, but it will be obtuse.
  return if request(1).blocked?
  yield
end