redis-memo 0.0.0.alpha → 0.0.0.beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/redis-memo.rb +5 -0
- data/lib/redis_memo.rb +96 -1
- data/lib/redis_memo/after_commit.rb +119 -0
- data/lib/redis_memo/batch.rb +54 -0
- data/lib/redis_memo/cache.rb +108 -0
- data/lib/redis_memo/connection_pool.rb +27 -0
- data/lib/redis_memo/future.rb +125 -0
- data/lib/redis_memo/memoizable.rb +119 -0
- data/lib/redis_memo/memoizable/dependency.rb +66 -0
- data/lib/redis_memo/memoizable/invalidation.rb +142 -0
- data/lib/redis_memo/memoize_method.rb +133 -0
- data/lib/redis_memo/memoize_query.rb +151 -0
- data/lib/redis_memo/memoize_query/cached_select.rb +380 -0
- data/lib/redis_memo/memoize_query/cached_select/bind_params.rb +127 -0
- data/lib/redis_memo/memoize_query/cached_select/connection_adapter.rb +41 -0
- data/lib/redis_memo/memoize_query/cached_select/statement_cache.rb +16 -0
- data/lib/redis_memo/memoize_query/invalidation.rb +211 -0
- data/lib/redis_memo/memoize_query/memoize_table_column.rb +5 -0
- data/lib/redis_memo/memoize_query/model_callback.rb +21 -0
- data/lib/redis_memo/middleware.rb +18 -0
- data/lib/redis_memo/options.rb +87 -0
- data/lib/redis_memo/redis.rb +67 -0
- data/lib/redis_memo/tracer.rb +25 -0
- metadata +106 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72bcf0679b811825222a6b38de0fcdadf4546640d2f61b18ebc827e37137dc41
|
4
|
+
data.tar.gz: 7a55c7bbd084a24afe085e85bc5cd5c486cf240094b64ca09d48e9e0ce126384
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a3af887512e4b6571829fcfda719e43abb917eb4773407bd27788b0b671a42263ec31c2f6286f0a1279a354851c8c8cb48eb9701cba59863e500ca47419080e
|
7
|
+
data.tar.gz: feeb72b177ec07953b839e6e0759cbdf42dfd4eddd19f89599e721589462e849e7685a659b03128504d670764223f1805402cb3ee8a96411b834f229ba837d99
|
data/lib/redis-memo.rb
ADDED
data/lib/redis_memo.rb
CHANGED
@@ -1,6 +1,101 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'digest'
|
5
|
+
require 'json'
|
6
|
+
require 'securerandom'
|
4
7
|
|
5
8
|
module RedisMemo
|
9
|
+
require 'redis_memo/memoize_method'
|
10
|
+
require 'redis_memo/memoize_query'
|
11
|
+
|
12
|
+
# A process-level +RedisMemo::Options+ instance that stores the global
|
13
|
+
# options. This object can be modified by +RedisMemo.configure+.
|
14
|
+
#
|
15
|
+
# +memoize_method+ allows users to provide method-level configuration.
|
16
|
+
# When no callsite-level configuration specified we will use the values in
|
17
|
+
# +DefaultOptions+ as the default value.
|
18
|
+
DefaultOptions = RedisMemo::Options.new
|
19
|
+
|
20
|
+
# @todo Move thread keys to +RedisMemo::ThreadKey+
|
21
|
+
THREAD_KEY_WITHOUT_MEMO = :__redis_memo_without_memo__
|
22
|
+
|
23
|
+
# Configure global-level default options. Those options will be used unless
|
24
|
+
# some options specified at +memoize_method+ callsite level. See
|
25
|
+
# +RedisMemo::Options+ for all the possible options.
|
26
|
+
#
|
27
|
+
# @yieldparam [RedisMemo::Options] default_options
|
28
|
+
# +RedisMemo::DefaultOptions+
|
29
|
+
# @return [void]
|
30
|
+
def self.configure(&blk)
|
31
|
+
blk.call(DefaultOptions)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Batch Redis calls triggered by +memoize_method+ to minimize the round trips
|
35
|
+
# to Redis.
|
36
|
+
# - Batches cannot be nested
|
37
|
+
# - When a batch is still open (while still in the +RedisMemo.batch+ block)
|
38
|
+
# the return value of any memoized method is a +RedisMemo::Future+ instead of
|
39
|
+
# the actual method value
|
40
|
+
# - The actual method values are returned as a list, in the same order as
|
41
|
+
# invoking, after exiting the block
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# results = RedisMemo.batch do
|
45
|
+
# 5.times { |i| memoized_calculation(i) }
|
46
|
+
# nil # Not the return value of the block
|
47
|
+
# end
|
48
|
+
# results.size == 5 # true
|
49
|
+
#
|
50
|
+
# See +RedisMemo::Batch+ for more information.
|
51
|
+
def self.batch(&blk)
|
52
|
+
RedisMemo::Batch.open
|
53
|
+
blk.call
|
54
|
+
RedisMemo::Batch.execute
|
55
|
+
ensure
|
56
|
+
RedisMemo::Batch.close
|
57
|
+
end
|
58
|
+
|
59
|
+
# @todo Move this method out of the top namespace
|
60
|
+
def self.checksum(serialized)
|
61
|
+
Digest::SHA1.base64digest(serialized)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @todo Move this method out of the top namespace
|
65
|
+
def self.uuid
|
66
|
+
SecureRandom.uuid
|
67
|
+
end
|
68
|
+
|
69
|
+
# @todo Move this method out of the top namespace
|
70
|
+
def self.deep_sort_hash(orig_hash)
|
71
|
+
{}.tap do |new_hash|
|
72
|
+
orig_hash.sort.each do |k, v|
|
73
|
+
new_hash[k] = v.is_a?(Hash) ? deep_sort_hash(v) : v
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Whether the current execution context has been configured to skip
|
79
|
+
# memoization and use the uncached code path.
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
82
|
+
def self.without_memo?
|
83
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] == true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Configure the wrapped code in the block to skip memoization.
|
87
|
+
#
|
88
|
+
# @yield [] no_args The block of code to skip memoization.
|
89
|
+
def self.without_memo
|
90
|
+
prev_value = Thread.current[THREAD_KEY_WITHOUT_MEMO]
|
91
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] = true
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] = prev_value
|
95
|
+
end
|
96
|
+
|
97
|
+
# @todo Move errors to a separate file errors.rb
|
98
|
+
class ArgumentError < ::ArgumentError; end
|
99
|
+
class RuntimeError < ::RuntimeError; end
|
100
|
+
class WithoutMemoization < Exception; end
|
6
101
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# TODO: -> RedisMemo::Memoizable::AfterCommit
|
3
|
+
|
4
|
+
class RedisMemo::AfterCommit
|
5
|
+
# We assume there's only one ActiveRecord DB connection used for opening
|
6
|
+
# transactions
|
7
|
+
@@callback_added = false
|
8
|
+
@@pending_memo_versions = {}
|
9
|
+
@@previous_memo_versions = {}
|
10
|
+
|
11
|
+
def self.connection
|
12
|
+
ActiveRecord::Base.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.pending_memo_versions
|
16
|
+
# In DB transactions, the pending memo version should be used immediately
|
17
|
+
# as part of method checksums. method_cache_keys made of
|
18
|
+
# pending_memo_versions are not referencable until we bump the
|
19
|
+
# pending_memo_versions after commiting the current transaction
|
20
|
+
@@pending_memo_versions
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.bump_memo_version_after_commit(key, version, previous_version:)
|
24
|
+
@@pending_memo_versions[key] = version
|
25
|
+
@@previous_memo_versions[key] = previous_version
|
26
|
+
|
27
|
+
reset_after_transaction
|
28
|
+
end
|
29
|
+
|
30
|
+
# https://github.com/Envek/after_commit_everywhere/blob/be8602f9fbb8e40b0fc8a04a47e4c2bc6b560ad5/lib/after_commit_everywhere.rb#L93
|
31
|
+
# Helper method to determine whether we're currently in transaction or not
|
32
|
+
def self.in_transaction?
|
33
|
+
# service transactions (tests and database_cleaner) are not joinable
|
34
|
+
connection.transaction_open? && connection.current_transaction.joinable?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.after_commit(&blk)
|
40
|
+
connection.add_transaction_record(
|
41
|
+
RedisMemo::AfterCommit::Callback.new(connection, committed: blk),
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.after_rollback(&blk)
|
46
|
+
connection.add_transaction_record(
|
47
|
+
RedisMemo::AfterCommit::Callback.new(connection, rolledback: blk),
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.reset_after_transaction
|
52
|
+
return if @@callback_added
|
53
|
+
@@callback_added = true
|
54
|
+
|
55
|
+
after_commit do
|
56
|
+
reset(commited: true)
|
57
|
+
end
|
58
|
+
|
59
|
+
after_rollback do
|
60
|
+
reset(commited: false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.reset(commited:)
|
65
|
+
if commited
|
66
|
+
@@pending_memo_versions.each do |key, version|
|
67
|
+
invalidation_queue =
|
68
|
+
RedisMemo::Memoizable::Invalidation.class_variable_get(:@@invalidation_queue)
|
69
|
+
|
70
|
+
invalidation_queue << RedisMemo::Memoizable::Invalidation::Task.new(
|
71
|
+
key,
|
72
|
+
version,
|
73
|
+
@@previous_memo_versions[key],
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
RedisMemo::Memoizable::Invalidation.drain_invalidation_queue
|
78
|
+
else
|
79
|
+
@@pending_memo_versions.each_key do |key|
|
80
|
+
RedisMemo::Cache.local_cache&.delete(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@@callback_added = false
|
84
|
+
@@pending_memo_versions.clear
|
85
|
+
@@previous_memo_versions.clear
|
86
|
+
end
|
87
|
+
|
88
|
+
# https://github.com/Envek/after_commit_everywhere/blob/master/lib/after_commit_everywhere/wrap.rb
|
89
|
+
class Callback
|
90
|
+
def initialize(connection, committed: nil, rolledback: nil)
|
91
|
+
@connection = connection
|
92
|
+
@committed = committed
|
93
|
+
@rolledback = rolledback
|
94
|
+
end
|
95
|
+
|
96
|
+
def has_transactional_callbacks?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def trigger_transactional_callbacks?
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def committed!(*)
|
105
|
+
@committed&.call
|
106
|
+
end
|
107
|
+
|
108
|
+
# Required for +transaction(requires_new: true)+
|
109
|
+
def add_to_transaction(*)
|
110
|
+
@connection.add_transaction_record(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
def before_committed!(*); end
|
114
|
+
|
115
|
+
def rolledback!(*)
|
116
|
+
@rolledback&.call
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'cache'
|
3
|
+
require_relative 'tracer'
|
4
|
+
|
5
|
+
class RedisMemo::Batch
|
6
|
+
THREAD_KEY = :__redis_memo_current_batch__
|
7
|
+
|
8
|
+
def self.open
|
9
|
+
if current
|
10
|
+
raise RedisMemo::RuntimeError, 'Batch can not be nested'
|
11
|
+
end
|
12
|
+
|
13
|
+
Thread.current[THREAD_KEY] = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.close
|
17
|
+
if current
|
18
|
+
futures = current
|
19
|
+
Thread.current[THREAD_KEY] = nil
|
20
|
+
futures
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.current
|
25
|
+
Thread.current[THREAD_KEY]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.execute
|
29
|
+
futures = close
|
30
|
+
return unless futures
|
31
|
+
|
32
|
+
cached_results = {}
|
33
|
+
method_cache_keys = nil
|
34
|
+
|
35
|
+
RedisMemo::Tracer.trace('redis_memo.cache.batch.read', nil) do
|
36
|
+
method_cache_keys = RedisMemo::MemoizeMethod.method_cache_keys(
|
37
|
+
futures.map(&:context),
|
38
|
+
)
|
39
|
+
|
40
|
+
if method_cache_keys
|
41
|
+
cached_results = RedisMemo::Cache.read_multi(*method_cache_keys)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
RedisMemo::Tracer.trace('redis_memo.cache.batch.execute', nil) do
|
46
|
+
results = Array.new(futures.size)
|
47
|
+
futures.each_with_index do |future, i|
|
48
|
+
future.method_cache_key = method_cache_keys ? method_cache_keys[i] : ''
|
49
|
+
results[i] = future.execute(cached_results)
|
50
|
+
end
|
51
|
+
results
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'options'
|
3
|
+
require_relative 'redis'
|
4
|
+
require_relative 'connection_pool'
|
5
|
+
|
6
|
+
class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore
|
7
|
+
class Rescuable < Exception; end
|
8
|
+
|
9
|
+
THREAD_KEY_LOCAL_CACHE = :__redis_memo_cache_local_cache__
|
10
|
+
THREAD_KEY_LOCAL_DEPENDENCY_CACHE = :__redis_memo_local_cache_dependency_cache__
|
11
|
+
THREAD_KEY_RAISE_ERROR = :__redis_memo_cache_raise_error__
|
12
|
+
|
13
|
+
@@redis = nil
|
14
|
+
@@redis_store = nil
|
15
|
+
|
16
|
+
@@redis_store_error_handler = proc do |method:, exception:, returning:|
|
17
|
+
RedisMemo::DefaultOptions.redis_error_handler&.call(exception, method)
|
18
|
+
RedisMemo::DefaultOptions.logger&.warn(exception.full_message)
|
19
|
+
|
20
|
+
if Thread.current[THREAD_KEY_RAISE_ERROR]
|
21
|
+
raise RedisMemo::Cache::Rescuable
|
22
|
+
else
|
23
|
+
returning
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.redis
|
28
|
+
@@redis ||=
|
29
|
+
if RedisMemo::DefaultOptions.connection_pool
|
30
|
+
RedisMemo::ConnectionPool.new(**RedisMemo::DefaultOptions.connection_pool)
|
31
|
+
else
|
32
|
+
RedisMemo::DefaultOptions.redis
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.redis_store
|
37
|
+
@@redis_store ||= new(
|
38
|
+
compress: RedisMemo::DefaultOptions.compress,
|
39
|
+
compress_threshold: RedisMemo::DefaultOptions.compress_threshold,
|
40
|
+
error_handler: @@redis_store_error_handler,
|
41
|
+
expires_in: RedisMemo::DefaultOptions.expires_in,
|
42
|
+
redis: redis,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
# We use our own local_cache instead of the one from RedisCacheStore, because
|
47
|
+
# the local_cache in RedisCacheStore stores a dumped
|
48
|
+
# ActiveSupport::Cache::Entry object -- which is slower comparing to a simple
|
49
|
+
# hash storing object references
|
50
|
+
def self.local_cache
|
51
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.local_dependency_cache
|
55
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE]
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def with_local_cache(&blk)
|
60
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE] = {}
|
61
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = {}
|
62
|
+
blk.call
|
63
|
+
ensure
|
64
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE] = nil
|
65
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# RedisCacheStore doesn't read from the local cache before reading from redis
|
69
|
+
def read_multi(*keys, raw: false, raise_error: false)
|
70
|
+
return {} if keys.empty?
|
71
|
+
|
72
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
73
|
+
|
74
|
+
local_entries = local_cache&.slice(*keys) || {}
|
75
|
+
|
76
|
+
keys_to_fetch = keys
|
77
|
+
keys_to_fetch -= local_entries.keys unless local_entries.empty?
|
78
|
+
return local_entries if keys_to_fetch.empty?
|
79
|
+
|
80
|
+
remote_entries = redis_store.read_multi(*keys_to_fetch, raw: raw)
|
81
|
+
local_cache&.merge!(remote_entries)
|
82
|
+
|
83
|
+
if local_entries.empty?
|
84
|
+
remote_entries
|
85
|
+
else
|
86
|
+
local_entries.merge!(remote_entries)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def write(key, value, disable_async: false, raise_error: false, **options)
|
91
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
92
|
+
|
93
|
+
if local_cache
|
94
|
+
local_cache[key] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
async = RedisMemo::DefaultOptions.async
|
98
|
+
if async.nil? || disable_async
|
99
|
+
redis_store.write(key, value, **options)
|
100
|
+
else
|
101
|
+
async.call do
|
102
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
103
|
+
redis_store.write(key, value, **options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'connection_pool'
|
3
|
+
require_relative 'redis'
|
4
|
+
|
5
|
+
class RedisMemo::ConnectionPool
|
6
|
+
def initialize(**options)
|
7
|
+
@connection_pool = ::ConnectionPool.new(**options) do
|
8
|
+
# Construct a new client every time the block gets called
|
9
|
+
RedisMemo::Redis.new(RedisMemo::DefaultOptions.redis_config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Avoid method_missing when possible for better performance
|
14
|
+
%i(get mget mapped_mget set eval).each do |method_name|
|
15
|
+
define_method method_name do |*args, &blk|
|
16
|
+
@connection_pool.with do |redis|
|
17
|
+
redis.send(method_name, *args, &blk)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method_name, *args, &blk)
|
23
|
+
@connection_pool.with do |redis|
|
24
|
+
redis.send(method_name, *args, &blk)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'cache'
|
3
|
+
require_relative 'tracer'
|
4
|
+
|
5
|
+
class RedisMemo::Future
|
6
|
+
attr_writer :method_cache_key
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
ref,
|
10
|
+
method_id,
|
11
|
+
method_args,
|
12
|
+
dependent_memos,
|
13
|
+
cache_options,
|
14
|
+
method_name_without_memo
|
15
|
+
)
|
16
|
+
@ref = ref
|
17
|
+
@method_id = method_id
|
18
|
+
@method_args = method_args
|
19
|
+
@dependent_memos = dependent_memos
|
20
|
+
@cache_options = cache_options
|
21
|
+
@method_name_without_memo = method_name_without_memo
|
22
|
+
@method_cache_key = nil
|
23
|
+
@cache_hit = false
|
24
|
+
@cached_result = nil
|
25
|
+
@computed_cached_result = false
|
26
|
+
@fresh_result = nil
|
27
|
+
@computed_fresh_result = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def context
|
31
|
+
[@method_id, @method_args, @dependent_memos]
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_cache_key
|
35
|
+
@method_cache_key ||=
|
36
|
+
RedisMemo::MemoizeMethod.method_cache_keys([context])&.first || ''
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(cached_results=nil)
|
40
|
+
if RedisMemo::Batch.current
|
41
|
+
raise RedisMemo::RuntimeError, 'Cannot execute future when a batch is still open'
|
42
|
+
end
|
43
|
+
|
44
|
+
if cache_hit?(cached_results)
|
45
|
+
validate_cache_result =
|
46
|
+
RedisMemo::DefaultOptions.cache_validation_sampler&.call(@method_id)
|
47
|
+
|
48
|
+
if validate_cache_result && cached_result != fresh_result
|
49
|
+
RedisMemo::DefaultOptions.cache_out_of_date_handler&.call(
|
50
|
+
@ref,
|
51
|
+
@method_id,
|
52
|
+
@method_args,
|
53
|
+
cached_result,
|
54
|
+
fresh_result,
|
55
|
+
)
|
56
|
+
return fresh_result
|
57
|
+
end
|
58
|
+
|
59
|
+
return cached_result
|
60
|
+
end
|
61
|
+
|
62
|
+
fresh_result
|
63
|
+
end
|
64
|
+
|
65
|
+
def result
|
66
|
+
unless @computed_cached_result
|
67
|
+
raise RedisMemo::RuntimeError, 'Future has not been executed'
|
68
|
+
end
|
69
|
+
|
70
|
+
@fresh_result || @cached_result
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def cache_hit?(cached_results=nil)
|
76
|
+
cached_result(cached_results)
|
77
|
+
|
78
|
+
@cache_hit
|
79
|
+
end
|
80
|
+
|
81
|
+
def cached_result(cached_results=nil)
|
82
|
+
return @cached_result if @computed_cached_result
|
83
|
+
|
84
|
+
@cache_hit = false
|
85
|
+
RedisMemo::Tracer.trace('redis_memo.cache.read', @method_id) do
|
86
|
+
# Calculate the method_cache_key now, or use the method_cache_key obtained
|
87
|
+
# from batching previously
|
88
|
+
if !method_cache_key.empty?
|
89
|
+
cached_results ||= RedisMemo::Cache.read_multi(method_cache_key)
|
90
|
+
@cache_hit = cached_results.include?(method_cache_key)
|
91
|
+
@cached_result = cached_results[method_cache_key]
|
92
|
+
end
|
93
|
+
RedisMemo::Tracer.set_tag(cache_hit: @cache_hit)
|
94
|
+
end
|
95
|
+
|
96
|
+
@computed_cached_result = true
|
97
|
+
@cached_result
|
98
|
+
end
|
99
|
+
|
100
|
+
def fresh_result
|
101
|
+
return @fresh_result if @computed_fresh_result
|
102
|
+
|
103
|
+
RedisMemo::Tracer.trace('redis_memo.cache.write', @method_id) do
|
104
|
+
# cache miss
|
105
|
+
@fresh_result = @ref.send(@method_name_without_memo, *@method_args)
|
106
|
+
if @cache_options.include?(:expires_in) && @cache_options[:expires_in].respond_to?(:call)
|
107
|
+
@cache_options[:expires_in] = @cache_options[:expires_in].call(@fresh_result)
|
108
|
+
end
|
109
|
+
|
110
|
+
if !method_cache_key.empty? && (
|
111
|
+
# Write back fresh result if cache miss
|
112
|
+
!@cache_hit || (
|
113
|
+
# or cached result is out of date (sampled to validate the cache
|
114
|
+
# result)
|
115
|
+
@cache_hit && @cached_result != @fresh_result
|
116
|
+
)
|
117
|
+
)
|
118
|
+
RedisMemo::Cache.write(method_cache_key, @fresh_result, **@cache_options)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@computed_fresh_result = true
|
123
|
+
@fresh_result
|
124
|
+
end
|
125
|
+
end
|