1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
|
# frozen_string_literal: true
require_relative 'time_helper/monotonic'
require_relative 'memory_store/container'
class Circuitbox
class MemoryStore
include TimeHelper::Monotonic
def initialize(compaction_frequency: 60)
@store = {}
@mutex = Mutex.new
@compaction_frequency = compaction_frequency
@compact_after = current_second + compaction_frequency
end
def store(key, value, opts = {})
@mutex.synchronize do
@store[key] = Container.new(value: value, expiry: opts.fetch(:expires, 0))
value
end
end
def increment(key, amount = 1, opts = {})
seconds_to_expire = opts.fetch(:expires, 0)
@mutex.synchronize do
existing_container = fetch_container(key)
# reusing the existing container is a small optimization
# to reduce the amount of objects created
if existing_container
existing_container.expires_after(seconds_to_expire)
existing_container.value += amount
else
@store[key] = Container.new(value: amount, expiry: seconds_to_expire)
amount
end
end
end
def load(key, _opts = {})
@mutex.synchronize { fetch_container(key)&.value }
end
def values_at(*keys, **_opts)
@mutex.synchronize do
current_time = current_second
keys.map! { |key| fetch_container(key, current_time)&.value }
end
end
def key?(key)
@mutex.synchronize { !fetch_container(key).nil? }
end
def delete(key)
@mutex.synchronize { @store.delete(key) }
end
private
def fetch_container(key, current_time = current_second)
compact(current_time) if @compact_after < current_time
container = @store[key]
return unless container
if container.expired_at?(current_time)
@store.delete(key)
nil
else
container
end
end
def compact(current_time)
@store.delete_if { |_, value| value.expired_at?(current_time) }
@compact_after = current_time + @compaction_frequency
end
end
end
|