2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-07-28 21:25:19 -05:00
|
|
|
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)
|
2019-12-03 03:05:53 -06:00
|
|
|
redis ||= Discourse.redis
|
2013-07-28 21:25:19 -05:00
|
|
|
|
|
|
|
redis_key = self.redis_key(key)
|
|
|
|
|
|
|
|
unless result = redis.get(redis_key)
|
|
|
|
redis_lock_key = self.redis_lock_key(key)
|
|
|
|
|
2020-03-09 11:37:49 -05:00
|
|
|
start = Time.now
|
2013-07-28 21:25:19 -05:00
|
|
|
got_lock = false
|
2013-11-25 17:21:41 -06:00
|
|
|
|
|
|
|
begin
|
2020-03-09 11:37:49 -05:00
|
|
|
while Time.now < start + MAX_WAIT && !got_lock
|
2013-11-25 17:21:41 -06:00
|
|
|
LOCK.synchronize do
|
2017-07-27 20:20:09 -05:00
|
|
|
got_lock = get_lock(redis, redis_lock_key)
|
2013-11-25 17:21:41 -06:00
|
|
|
end
|
|
|
|
sleep 0.001
|
|
|
|
end
|
|
|
|
|
|
|
|
unless result = redis.get(redis_key)
|
|
|
|
result = yield
|
|
|
|
redis.setex(redis_key, duration, result)
|
2013-07-28 21:25:19 -05:00
|
|
|
end
|
|
|
|
|
2013-11-25 17:21:41 -06:00
|
|
|
ensure
|
2015-09-21 13:28:20 -05:00
|
|
|
# NOTE: delete regardless so next one in does not need to wait MAX_WAIT again
|
2013-11-25 17:21:41 -06:00
|
|
|
redis.del(redis_lock_key)
|
2013-07-28 21:25:19 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.redis_lock_key(key)
|
2019-05-02 17:17:27 -05:00
|
|
|
+"memoize_lock_" << key
|
2013-07-28 21:25:19 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.redis_key(key)
|
2019-05-02 17:17:27 -05:00
|
|
|
+"memoize_" << key
|
2013-07-28 21:25:19 -05:00
|
|
|
end
|
|
|
|
|
2019-02-21 01:03:55 -06:00
|
|
|
# Used for testing
|
|
|
|
def self.flush!
|
2019-12-03 03:05:53 -06:00
|
|
|
Discourse.redis.scan_each(match: "memoize_*").each { |key| Discourse.redis.del(key) }
|
2019-02-21 01:03:55 -06:00
|
|
|
end
|
|
|
|
|
2013-07-28 21:25:19 -05:00
|
|
|
protected
|
2015-09-21 13:28:20 -05:00
|
|
|
|
2013-07-28 21:25:19 -05:00
|
|
|
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
|
2015-09-21 13:28:20 -05:00
|
|
|
result == ["OK"]
|
2013-07-28 21:25:19 -05:00
|
|
|
end
|
|
|
|
end
|