Class: Pecorino::Adapters::MemoryAdapter

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

Overview

A memory store for leaky buckets and blocks

Defined Under Namespace

Classes: KeyedLock

Instance Method Summary collapse

Constructor Details

#initializeMemoryAdapter

Returns a new instance of MemoryAdapter.



35
36
37
38
39
# File 'lib/pecorino/adapters/memory_adapter.rb', line 35

def initialize
  @buckets = {}
  @blocks = {}
  @lock = KeyedLock.new
end

Instance Method Details

#add_tokens(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object

Adds tokens to the leaky bucket. The return value is a tuple of two values: the current level (Float) and whether the bucket is now at capacity (Boolean)



57
58
59
# File 'lib/pecorino/adapters/memory_adapter.rb', line 57

def add_tokens(key:, capacity:, leak_rate:, n_tokens:)
  add_tokens_with_lock(key, capacity, leak_rate, n_tokens, _conditionally = false)
end

#add_tokens_conditionally(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object

Adds tokens to the leaky bucket conditionally. If there is capacity, the tokens will be added. If there isn’t - the fillup will be rejected. The return value is a triplet of the current level (Float), whether the bucket is now at capacity (Boolean) and whether the fillup was accepted (Boolean)



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

def add_tokens_conditionally(key:, capacity:, leak_rate:, n_tokens:)
  add_tokens_with_lock(key, capacity, leak_rate, n_tokens, _conditionally = true)
end

#blocked_until(key:) ⇒ Object

Returns the time until which a block for a given key is in effect. If there is no block in effect, the method should return ‘nil`. The return value is either a `Time` or `nil`



82
83
84
85
86
87
88
89
90
# File 'lib/pecorino/adapters/memory_adapter.rb', line 82

def blocked_until(key:)
  blocked_until_monotonic = @blocks[key]
  return unless blocked_until_monotonic

  now_monotonic = get_mono_time
  return unless blocked_until_monotonic > now_monotonic

  Time.now + (blocked_until_monotonic - now_monotonic)
end

#create_tables(active_record_schema) ⇒ Object

No-op



112
113
# File 'lib/pecorino/adapters/memory_adapter.rb', line 112

def create_tables(active_record_schema)
end

#pruneObject

Deletes leaky buckets which have an expiry value prior to now and throttle blocks which have now lapsed



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/pecorino/adapters/memory_adapter.rb', line 94

def prune
  now_monotonic = get_mono_time

  @blocks.keys.each do |key|
    @lock.with(key) do
      @blocks.delete(key) if @blocks[key] && @blocks[key] < now_monotonic
    end
  end

  @buckets.keys.each do |key|
    @lock.with(key) do
      _level, expire_at_monotonic = @buckets[key]
      @buckets.delete(key) if expire_at_monotonic && expire_at_monotonic < now_monotonic
    end
  end
end

#set_block(key:, block_for:) ⇒ Object

Sets a timed block for the given key - this is used when a throttle fires. The return value is not defined - the call should always succeed.



71
72
73
74
75
76
77
78
# File 'lib/pecorino/adapters/memory_adapter.rb', line 71

def set_block(key:, block_for:)
  raise ArgumentError, "block_for must be positive" unless block_for > 0
  @lock.lock(key)
  @blocks[key] = get_mono_time + block_for.to_f
  Time.now + block_for.to_f
ensure
  @lock.unlock(key)
end

#state(key:, capacity:, leak_rate:) ⇒ Object

Returns the state of a leaky bucket. The state should be a tuple of two values: the current level (Float) and whether the bucket is now at capacity (Boolean)



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/pecorino/adapters/memory_adapter.rb', line 43

def state(key:, capacity:, leak_rate:)
  @lock.lock(key)
  level, ts = @buckets[key]
  @lock.unlock(key)

  return [0, false] unless level

  dt = get_mono_time - ts
  level_after_leak = [0, level - (leak_rate * dt)].max
  [level_after_leak.to_f, (level_after_leak - capacity) >= 0]
end