discourse/lib/distributed_memoizer.rb
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

73 lines
1.5 KiB
Ruby

# frozen_string_literal: true
class DistributedMemoizer
# never wait for longer that 1 second for a cross process lock
MAX_WAIT = 2
LOCK = Mutex.new
# memoize a key across processes and machines
def self.memoize(key, duration = 60 * 60 * 24, redis = nil)
redis ||= $redis
redis_key = self.redis_key(key)
unless result = redis.get(redis_key)
redis_lock_key = self.redis_lock_key(key)
start = Time.new
got_lock = false
begin
while Time.new < start + MAX_WAIT && !got_lock
LOCK.synchronize do
got_lock = get_lock(redis, redis_lock_key)
end
sleep 0.001
end
unless result = redis.get(redis_key)
result = yield
redis.setex(redis_key, duration, result)
end
ensure
# NOTE: delete regardless so next one in does not need to wait MAX_WAIT again
redis.del(redis_lock_key)
end
end
result
end
def self.redis_lock_key(key)
+"memoize_lock_" << key
end
def self.redis_key(key)
+"memoize_" << key
end
# Used for testing
def self.flush!
$redis.scan_each(match: "memoize_*").each { |key| $redis.del(key) }
end
protected
def self.get_lock(redis, redis_lock_key)
redis.watch(redis_lock_key)
current = redis.get(redis_lock_key)
return false if current
unique = SecureRandom.hex
result = redis.multi do
redis.setex(redis_lock_key, MAX_WAIT, unique)
end
redis.unwatch
result == ["OK"]
end
end