safe_memoize 0.1.2 → 0.3.0
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/CHANGELOG.md +33 -0
- data/README.md +191 -0
- data/lib/safe_memoize/cache_metrics_methods.rb +30 -0
- data/lib/safe_memoize/cache_record_methods.rb +52 -0
- data/lib/safe_memoize/cache_store_methods.rb +70 -0
- data/lib/safe_memoize/class_methods.rb +113 -0
- data/lib/safe_memoize/custom_key_methods.rb +39 -0
- data/lib/safe_memoize/hooks_methods.rb +33 -0
- data/lib/safe_memoize/inspection_methods.rb +68 -0
- data/lib/safe_memoize/instance_methods.rb +16 -0
- data/lib/safe_memoize/lru_methods.rb +46 -0
- data/lib/safe_memoize/public_custom_key_methods.rb +23 -0
- data/lib/safe_memoize/public_methods.rb +94 -0
- data/lib/safe_memoize/public_metrics_methods.rb +131 -0
- data/lib/safe_memoize/version.rb +1 -1
- data/lib/safe_memoize.rb +14 -195
- data/sig/safe_memoize.rbs +132 -33
- metadata +13 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SafeMemoize
|
|
4
|
+
module LruMethods
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# Per-method LRU order: { method_name => { cache_key => true, ... } }
|
|
8
|
+
# Ruby Hash insertion order gives LRU for free: oldest key first, newest last.
|
|
9
|
+
def lru_order_store
|
|
10
|
+
@__safe_memo_lru_order__ ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Mark +cache_key+ as most recently used for +method_name+.
|
|
14
|
+
def lru_touch(method_name, cache_key)
|
|
15
|
+
method_store = lru_order_store[method_name] ||= {}
|
|
16
|
+
method_store.delete(cache_key)
|
|
17
|
+
method_store[cache_key] = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Evict the least-recently-used entry for +method_name+ when at +max_size+.
|
|
21
|
+
# Must be called while holding the mutex.
|
|
22
|
+
def lru_evict_if_over_limit(method_name, max_size)
|
|
23
|
+
method_store = lru_order_store[method_name]
|
|
24
|
+
return unless method_store && !method_store.empty?
|
|
25
|
+
|
|
26
|
+
cache = @__safe_memo_cache__
|
|
27
|
+
|
|
28
|
+
# Prune stale LRU references left behind by reset_memo calls.
|
|
29
|
+
method_store.delete_if { |key, _| !cache&.key?(key) }
|
|
30
|
+
|
|
31
|
+
return if method_store.size < max_size
|
|
32
|
+
|
|
33
|
+
lru_cache_key = method_store.keys.first
|
|
34
|
+
return unless lru_cache_key
|
|
35
|
+
|
|
36
|
+
method_store.delete(lru_cache_key)
|
|
37
|
+
record = cache&.delete(lru_cache_key)
|
|
38
|
+
call_memo_hooks(:on_evict, lru_cache_key, record) if record
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Clear all LRU tracking state. Called by reset_all_memos.
|
|
42
|
+
def lru_clear_all
|
|
43
|
+
@__safe_memo_lru_order__ = {}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SafeMemoize
|
|
4
|
+
module PublicCustomKeyMethods
|
|
5
|
+
def memoize_with_custom_key(method_name, &key_generator)
|
|
6
|
+
raise ArgumentError, "block required for key generation" unless key_generator
|
|
7
|
+
|
|
8
|
+
register_custom_key(method_name, &key_generator)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def clear_custom_keys(method_name = nil)
|
|
12
|
+
if method_name
|
|
13
|
+
with_memo_lock do
|
|
14
|
+
custom_key_store.delete(method_name.to_sym)
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
with_memo_lock do
|
|
18
|
+
_clear_custom_keys
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SafeMemoize
|
|
4
|
+
module PublicMethods
|
|
5
|
+
def memoized?(method_name, *args, **kwargs, &block)
|
|
6
|
+
return false if block
|
|
7
|
+
|
|
8
|
+
cache_key = safe_memo_cache_key(method_name, args, kwargs)
|
|
9
|
+
|
|
10
|
+
with_memo_lock do
|
|
11
|
+
memo_cache_hit?(cache_key)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def memo_count(*method_name)
|
|
16
|
+
scoped_method = safe_memo_scoped_method(method_name)
|
|
17
|
+
|
|
18
|
+
with_memo_lock do
|
|
19
|
+
safe_memo_count_for(scoped_method)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def memo_keys(*method_name)
|
|
24
|
+
scoped_method = safe_memo_scoped_method(method_name)
|
|
25
|
+
|
|
26
|
+
with_memo_lock do
|
|
27
|
+
safe_memo_keys_for(scoped_method)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def memo_values(*method_name)
|
|
32
|
+
scoped_method = safe_memo_scoped_method(method_name)
|
|
33
|
+
|
|
34
|
+
with_memo_lock do
|
|
35
|
+
safe_memo_values_for(scoped_method)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def on_memo_expire(&block)
|
|
40
|
+
raise ArgumentError, "block required" unless block
|
|
41
|
+
|
|
42
|
+
register_memo_hook(:on_expire, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def on_memo_evict(&block)
|
|
46
|
+
raise ArgumentError, "block required" unless block
|
|
47
|
+
|
|
48
|
+
register_memo_hook(:on_evict, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def on_memo_hit(&block)
|
|
52
|
+
raise ArgumentError, "block required" unless block
|
|
53
|
+
|
|
54
|
+
register_memo_hook(:on_hit, &block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def clear_memo_hooks(hook_type = nil)
|
|
58
|
+
with_memo_lock do
|
|
59
|
+
_clear_memo_hooks(hook_type)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def reset_memo(method_name, *args, **kwargs)
|
|
64
|
+
method_name = method_name.to_sym
|
|
65
|
+
|
|
66
|
+
matcher = memo_matcher_for(method_name, args, kwargs)
|
|
67
|
+
|
|
68
|
+
with_memo_lock do
|
|
69
|
+
with_memo_cache do |cache|
|
|
70
|
+
cache.delete_if do |key, record|
|
|
71
|
+
if matcher.call(key)
|
|
72
|
+
call_memo_hooks(:on_evict, key, record)
|
|
73
|
+
true
|
|
74
|
+
else
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def reset_all_memos
|
|
83
|
+
with_memo_lock do
|
|
84
|
+
if defined?(@__safe_memo_cache__) && @__safe_memo_cache__
|
|
85
|
+
@__safe_memo_cache__.each do |key, record|
|
|
86
|
+
call_memo_hooks(:on_evict, key, record)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
@__safe_memo_cache__ = {}
|
|
90
|
+
lru_clear_all
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SafeMemoize
|
|
4
|
+
module PublicMetricsMethods
|
|
5
|
+
def cache_stats
|
|
6
|
+
with_memo_lock do
|
|
7
|
+
metrics = memo_metrics_store
|
|
8
|
+
|
|
9
|
+
if metrics.empty?
|
|
10
|
+
return {
|
|
11
|
+
total_hits: 0,
|
|
12
|
+
total_misses: 0,
|
|
13
|
+
hit_rate: 0.0,
|
|
14
|
+
miss_rate: 0.0,
|
|
15
|
+
average_computation_time: 0.0,
|
|
16
|
+
entries: []
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
total_hits = metrics.values.sum { |m| m[:hits] }
|
|
21
|
+
total_misses = metrics.values.sum { |m| m[:misses] }
|
|
22
|
+
total_time = metrics.values.sum { |m| m[:total_time] }
|
|
23
|
+
total_calls = total_hits + total_misses
|
|
24
|
+
|
|
25
|
+
hit_rate = total_calls.zero? ? 0.0 : (total_hits.to_f / total_calls * 100).round(2)
|
|
26
|
+
miss_rate = total_calls.zero? ? 0.0 : (total_misses.to_f / total_calls * 100).round(2)
|
|
27
|
+
avg_time = total_misses.zero? ? 0.0 : (total_time / total_misses).round(6)
|
|
28
|
+
|
|
29
|
+
entries = metrics.map do |cache_key, stats|
|
|
30
|
+
method_name, args, _kwargs = cache_key
|
|
31
|
+
entry_hit_rate = if (stats[:hits] + stats[:misses]).zero?
|
|
32
|
+
0.0
|
|
33
|
+
else
|
|
34
|
+
(stats[:hits].to_f / (stats[:hits] + stats[:misses]) * 100).round(2)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
method: method_name,
|
|
39
|
+
args: args,
|
|
40
|
+
hits: stats[:hits],
|
|
41
|
+
misses: stats[:misses],
|
|
42
|
+
hit_rate: entry_hit_rate,
|
|
43
|
+
computation_time: stats[:total_time].round(6)
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
total_hits: total_hits,
|
|
49
|
+
total_misses: total_misses,
|
|
50
|
+
hit_rate: hit_rate,
|
|
51
|
+
miss_rate: miss_rate,
|
|
52
|
+
average_computation_time: avg_time,
|
|
53
|
+
entries: entries
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cache_stats_for(method_name)
|
|
59
|
+
method_name = method_name.to_sym
|
|
60
|
+
|
|
61
|
+
with_memo_lock do
|
|
62
|
+
metrics = memo_metrics_store
|
|
63
|
+
method_metrics = metrics.select { |key, _| key[0] == method_name }
|
|
64
|
+
|
|
65
|
+
if method_metrics.empty?
|
|
66
|
+
return {
|
|
67
|
+
method: method_name,
|
|
68
|
+
total_hits: 0,
|
|
69
|
+
total_misses: 0,
|
|
70
|
+
hit_rate: 0.0,
|
|
71
|
+
miss_rate: 0.0,
|
|
72
|
+
average_computation_time: 0.0,
|
|
73
|
+
entries: []
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
total_hits = method_metrics.values.sum { |m| m[:hits] }
|
|
78
|
+
total_misses = method_metrics.values.sum { |m| m[:misses] }
|
|
79
|
+
total_time = method_metrics.values.sum { |m| m[:total_time] }
|
|
80
|
+
total_calls = total_hits + total_misses
|
|
81
|
+
|
|
82
|
+
hit_rate = total_calls.zero? ? 0.0 : (total_hits.to_f / total_calls * 100).round(2)
|
|
83
|
+
miss_rate = total_calls.zero? ? 0.0 : (total_misses.to_f / total_calls * 100).round(2)
|
|
84
|
+
avg_time = total_misses.zero? ? 0.0 : (total_time / total_misses).round(6)
|
|
85
|
+
|
|
86
|
+
entries = method_metrics.map do |cache_key, stats|
|
|
87
|
+
_method, args, _kwargs = cache_key
|
|
88
|
+
entry_hit_rate = if (stats[:hits] + stats[:misses]).zero?
|
|
89
|
+
0.0
|
|
90
|
+
else
|
|
91
|
+
(stats[:hits].to_f / (stats[:hits] + stats[:misses]) * 100).round(2)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
args: args,
|
|
96
|
+
hits: stats[:hits],
|
|
97
|
+
misses: stats[:misses],
|
|
98
|
+
hit_rate: entry_hit_rate,
|
|
99
|
+
computation_time: stats[:total_time].round(6)
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
method: method_name,
|
|
105
|
+
total_hits: total_hits,
|
|
106
|
+
total_misses: total_misses,
|
|
107
|
+
hit_rate: hit_rate,
|
|
108
|
+
miss_rate: miss_rate,
|
|
109
|
+
average_computation_time: avg_time,
|
|
110
|
+
entries: entries
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def cache_hit_rate
|
|
116
|
+
stats = cache_stats
|
|
117
|
+
stats[:hit_rate]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def cache_miss_rate
|
|
121
|
+
stats = cache_stats
|
|
122
|
+
stats[:miss_rate]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def cache_metrics_reset
|
|
126
|
+
with_memo_lock do
|
|
127
|
+
_reset_cache_metrics
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/safe_memoize/version.rb
CHANGED
data/lib/safe_memoize.rb
CHANGED
|
@@ -1,206 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "safe_memoize/version"
|
|
4
|
+
require_relative "safe_memoize/class_methods"
|
|
5
|
+
require_relative "safe_memoize/public_methods"
|
|
6
|
+
require_relative "safe_memoize/cache_store_methods"
|
|
7
|
+
require_relative "safe_memoize/cache_record_methods"
|
|
8
|
+
require_relative "safe_memoize/inspection_methods"
|
|
9
|
+
require_relative "safe_memoize/hooks_methods"
|
|
10
|
+
require_relative "safe_memoize/cache_metrics_methods"
|
|
11
|
+
require_relative "safe_memoize/public_metrics_methods"
|
|
12
|
+
require_relative "safe_memoize/custom_key_methods"
|
|
13
|
+
require_relative "safe_memoize/public_custom_key_methods"
|
|
14
|
+
require_relative "safe_memoize/lru_methods"
|
|
15
|
+
require_relative "safe_memoize/instance_methods"
|
|
4
16
|
|
|
5
17
|
module SafeMemoize
|
|
6
18
|
class Error < StandardError; end
|
|
7
19
|
|
|
20
|
+
include InstanceMethods
|
|
21
|
+
|
|
8
22
|
def self.prepended(base)
|
|
9
23
|
base.extend(ClassMethods)
|
|
10
24
|
end
|
|
11
|
-
|
|
12
|
-
module ClassMethods
|
|
13
|
-
def memoize(method_name)
|
|
14
|
-
method_name = method_name.to_sym
|
|
15
|
-
visibility = memoized_method_visibility(method_name)
|
|
16
|
-
|
|
17
|
-
mod = Module.new do
|
|
18
|
-
define_method(method_name) do |*args, **kwargs, &block|
|
|
19
|
-
# Blocks bypass cache entirely — they aren't comparable
|
|
20
|
-
return super(*args, **kwargs, &block) if block
|
|
21
|
-
|
|
22
|
-
cache_key = safe_memo_cache_key(method_name, args, kwargs)
|
|
23
|
-
|
|
24
|
-
# Fast path: check without lock
|
|
25
|
-
return memo_cache_read(cache_key) if memo_cache_hit?(cache_key)
|
|
26
|
-
|
|
27
|
-
memo_fetch_or_store(cache_key) { super(*args, **kwargs) }
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
send(visibility, method_name)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
prepend mod
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def memoized_method_visibility(method_name)
|
|
39
|
-
return :private if private_method_defined?(method_name)
|
|
40
|
-
return :protected if protected_method_defined?(method_name)
|
|
41
|
-
|
|
42
|
-
:public
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def memoized?(method_name, *args, **kwargs, &block)
|
|
47
|
-
return false if block
|
|
48
|
-
|
|
49
|
-
cache_key = safe_memo_cache_key(method_name, args, kwargs)
|
|
50
|
-
|
|
51
|
-
with_memo_lock do
|
|
52
|
-
with_memo_cache { |cache| cache.key?(cache_key) } || false
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def memo_count(*method_name)
|
|
57
|
-
scoped_method = safe_memo_scoped_method(method_name)
|
|
58
|
-
|
|
59
|
-
with_memo_lock do
|
|
60
|
-
safe_memo_count_for(scoped_method)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def memo_keys(*method_name)
|
|
65
|
-
scoped_method = safe_memo_scoped_method(method_name)
|
|
66
|
-
|
|
67
|
-
with_memo_lock do
|
|
68
|
-
safe_memo_keys_for(scoped_method)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def memo_values(*method_name)
|
|
73
|
-
scoped_method = safe_memo_scoped_method(method_name)
|
|
74
|
-
|
|
75
|
-
with_memo_lock do
|
|
76
|
-
safe_memo_values_for(scoped_method)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def reset_memo(method_name, *args, **kwargs)
|
|
81
|
-
method_name = method_name.to_sym
|
|
82
|
-
|
|
83
|
-
matcher = memo_matcher_for(method_name, args, kwargs)
|
|
84
|
-
|
|
85
|
-
with_memo_lock do
|
|
86
|
-
with_memo_cache do |cache|
|
|
87
|
-
cache.delete_if { |key, _| matcher.call(key) }
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def reset_all_memos
|
|
93
|
-
with_memo_lock do
|
|
94
|
-
@__safe_memo_cache__ = {}
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
private
|
|
99
|
-
|
|
100
|
-
def safe_memo_scoped_method(method_name)
|
|
101
|
-
raise ArgumentError, "expected 0 or 1 arguments" if method_name.length > 1
|
|
102
|
-
|
|
103
|
-
method_name.first&.to_sym
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def with_memo_lock
|
|
107
|
-
if defined?(@__safe_memo_mutex__) && @__safe_memo_mutex__
|
|
108
|
-
@__safe_memo_mutex__.synchronize { yield }
|
|
109
|
-
else
|
|
110
|
-
yield
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def memo_cache_or_nil
|
|
115
|
-
return nil unless defined?(@__safe_memo_cache__)
|
|
116
|
-
|
|
117
|
-
@__safe_memo_cache__
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def memo_cache_hit?(cache_key)
|
|
121
|
-
cache = memo_cache_or_nil
|
|
122
|
-
cache&.key?(cache_key)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def memo_cache_read(cache_key)
|
|
126
|
-
cache = memo_cache_or_nil
|
|
127
|
-
cache && cache[cache_key]
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def memo_fetch_or_store(cache_key)
|
|
131
|
-
memo_mutex!.synchronize do
|
|
132
|
-
@__safe_memo_cache__ ||= {}
|
|
133
|
-
|
|
134
|
-
if @__safe_memo_cache__.key?(cache_key)
|
|
135
|
-
@__safe_memo_cache__[cache_key]
|
|
136
|
-
else
|
|
137
|
-
@__safe_memo_cache__[cache_key] = yield
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def memo_mutex!
|
|
143
|
-
@__safe_memo_mutex__ ||= Mutex.new
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def with_memo_cache
|
|
147
|
-
cache = memo_cache_or_nil
|
|
148
|
-
return nil unless cache
|
|
149
|
-
|
|
150
|
-
yield cache
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def memo_matcher_for(method_name, args, kwargs)
|
|
154
|
-
if args.empty? && kwargs.empty?
|
|
155
|
-
->(key) { key[0] == method_name }
|
|
156
|
-
else
|
|
157
|
-
cache_key = safe_memo_cache_key(method_name, args, kwargs)
|
|
158
|
-
->(key) { key == cache_key }
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def memo_entries_for(method_name)
|
|
163
|
-
cache = memo_cache_or_nil
|
|
164
|
-
return [] unless cache
|
|
165
|
-
|
|
166
|
-
entries = cache.to_a
|
|
167
|
-
return entries unless method_name
|
|
168
|
-
|
|
169
|
-
entries.select { |(cache_key, _)| cache_key[0] == method_name }
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def safe_memo_count_for(method_name)
|
|
173
|
-
memo_entries_for(method_name).length
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def safe_memo_keys_for(method_name)
|
|
177
|
-
entries = memo_entries_for(method_name)
|
|
178
|
-
include_method = method_name.nil?
|
|
179
|
-
|
|
180
|
-
entries.map do |(cache_key, value)|
|
|
181
|
-
memo_projection(cache_key, value, include_method: include_method, include_value: false)
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def safe_memo_values_for(method_name)
|
|
186
|
-
entries = memo_entries_for(method_name)
|
|
187
|
-
include_method = method_name.nil?
|
|
188
|
-
|
|
189
|
-
entries.map do |(cache_key, value)|
|
|
190
|
-
memo_projection(cache_key, value, include_method: include_method, include_value: true)
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def memo_projection(cache_key, value, include_method:, include_value:)
|
|
195
|
-
method_name, args, kwargs = cache_key
|
|
196
|
-
|
|
197
|
-
payload = {args: args, kwargs: kwargs}
|
|
198
|
-
payload[:method] = method_name if include_method
|
|
199
|
-
payload[:value] = value if include_value
|
|
200
|
-
payload
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def safe_memo_cache_key(method_name, args, kwargs)
|
|
204
|
-
[method_name.to_sym, args, kwargs]
|
|
205
|
-
end
|
|
206
25
|
end
|
data/sig/safe_memoize.rbs
CHANGED
|
@@ -1,47 +1,146 @@
|
|
|
1
1
|
module SafeMemoize
|
|
2
2
|
VERSION: String
|
|
3
|
+
include InstanceMethods
|
|
3
4
|
|
|
4
|
-
type
|
|
5
|
-
type
|
|
5
|
+
type default_memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
|
|
6
|
+
type custom_memo_key = [Symbol, untyped]
|
|
7
|
+
type memo_key = default_memo_key | custom_memo_key
|
|
8
|
+
type memo_record = { value: untyped, expires_at: Float? }
|
|
6
9
|
|
|
7
|
-
@__safe_memo_cache__: Hash[memo_key,
|
|
10
|
+
@__safe_memo_cache__: Hash[memo_key, memo_record]?
|
|
8
11
|
@__safe_memo_mutex__: Mutex?
|
|
9
12
|
|
|
10
13
|
def self.prepended: (Class base) -> void
|
|
11
14
|
|
|
12
|
-
def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) -> bool
|
|
13
|
-
def memo_count: () -> Integer
|
|
14
|
-
def memo_count: (Symbol | String method_name) -> Integer
|
|
15
|
-
def memo_keys: () -> Array[untyped]
|
|
16
|
-
def memo_keys: (Symbol | String method_name) -> Array[untyped]
|
|
17
|
-
def memo_values: () -> Array[untyped]
|
|
18
|
-
def memo_values: (Symbol | String method_name) -> Array[untyped]
|
|
19
|
-
def reset_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
|
|
20
|
-
def reset_all_memos: () -> void
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def safe_memo_scoped_method: (Array[untyped] method_name) -> Symbol?
|
|
25
|
-
def with_memo_lock: { () -> untyped } -> untyped
|
|
26
|
-
def memo_cache_or_nil: () -> Hash[memo_key, untyped]?
|
|
27
|
-
def memo_cache_hit?: (memo_key cache_key) -> bool
|
|
28
|
-
def memo_cache_read: (memo_key cache_key) -> untyped?
|
|
29
|
-
def memo_fetch_or_store: (memo_key cache_key) { () -> untyped } -> untyped
|
|
30
|
-
def memo_mutex!: () -> Mutex
|
|
31
|
-
def with_memo_cache: { (Hash[memo_key, untyped] cache) -> untyped } -> untyped?
|
|
32
|
-
def memo_matcher_for: (Symbol method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> ((memo_key) -> bool)
|
|
33
|
-
def memo_entries_for: (Symbol? method_name) -> Array[memo_entry]
|
|
34
|
-
def safe_memo_count_for: (Symbol? method_name) -> Integer
|
|
35
|
-
def safe_memo_keys_for: (Symbol? method_name) -> Array[untyped]
|
|
36
|
-
def safe_memo_values_for: (Symbol? method_name) -> Array[untyped]
|
|
37
|
-
def memo_projection: (memo_key cache_key, untyped value, include_method: bool, include_value: bool) -> Hash[Symbol, untyped]
|
|
38
|
-
def safe_memo_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> memo_key
|
|
39
|
-
|
|
40
15
|
module ClassMethods
|
|
41
|
-
def memoize: (Symbol | String method_name) -> void
|
|
16
|
+
def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?) -> void
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def memoized_method_visibility: (Symbol method_name) -> Symbol
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module PublicMethods
|
|
24
|
+
def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
|
|
25
|
+
def memo_count: (*untyped method_name) -> Integer
|
|
26
|
+
def memo_keys: (*untyped method_name) -> Array[untyped]
|
|
27
|
+
def memo_values: (*untyped method_name) -> Array[untyped]
|
|
28
|
+
def on_memo_expire: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
29
|
+
def on_memo_evict: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
30
|
+
def on_memo_hit: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
31
|
+
def clear_memo_hooks: (Symbol? hook_type) -> void
|
|
32
|
+
def reset_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
|
|
33
|
+
def reset_all_memos: () -> void
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module CacheStoreMethods
|
|
37
|
+
@__safe_memo_cache__: Hash[memo_key, memo_record]?
|
|
38
|
+
@__safe_memo_mutex__: Mutex?
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def with_memo_lock: { () -> untyped } -> untyped
|
|
43
|
+
def memo_cache_or_nil: () -> Hash[memo_key, memo_record]?
|
|
44
|
+
def memo_cache_hit?: (memo_key cache_key) -> bool
|
|
45
|
+
def memo_cache_record: (memo_key cache_key) -> memo_record?
|
|
46
|
+
def memo_cache_read: (memo_key cache_key) -> untyped?
|
|
47
|
+
def memo_fetch_or_store: (memo_key cache_key) { () -> untyped } -> untyped
|
|
48
|
+
def memo_mutex!: () -> Mutex
|
|
49
|
+
def with_memo_cache: { (Hash[memo_key, memo_record] cache) -> untyped } -> untyped?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module CacheRecordMethods
|
|
53
|
+
private
|
|
42
54
|
|
|
55
|
+
def memo_ttl: (Numeric? ttl) -> Float?
|
|
56
|
+
def memo_expires_at: (Float? ttl) -> Float?
|
|
57
|
+
def memo_record: (untyped value, expires_at: Float?) -> memo_record
|
|
58
|
+
def memo_record_value: (memo_record record) -> untyped
|
|
59
|
+
def memo_record_live?: (memo_record? record) -> bool
|
|
60
|
+
def memo_prune_expired_entries!: (Hash[memo_key, memo_record] cache) -> void
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
module InspectionMethods
|
|
43
64
|
private
|
|
44
65
|
|
|
45
|
-
def
|
|
66
|
+
def safe_memo_scoped_method: (Array[untyped] method_name) -> Symbol?
|
|
67
|
+
def memo_matcher_for: (Symbol method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
|
|
68
|
+
def memo_entries_for: (Symbol? method_name) -> Array[untyped]
|
|
69
|
+
def safe_memo_count_for: (Symbol? method_name) -> Integer
|
|
70
|
+
def safe_memo_keys_for: (Symbol? method_name) -> Array[untyped]
|
|
71
|
+
def safe_memo_values_for: (Symbol? method_name) -> Array[untyped]
|
|
72
|
+
def memo_projection: (memo_key cache_key, memo_record value, include_method: bool, include_value: bool) -> Hash[Symbol, untyped]
|
|
73
|
+
def safe_memo_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> default_memo_key
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
module HooksMethods
|
|
77
|
+
@__safe_memo_hooks__: { on_expire: Array[Proc], on_evict: Array[Proc], on_hit: Array[Proc] }?
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def memo_hook_store: () -> { on_expire: Array[Proc], on_evict: Array[Proc], on_hit: Array[Proc] }
|
|
82
|
+
def register_memo_hook: (Symbol hook_type) { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
83
|
+
def call_memo_hooks: (Symbol hook_type, memo_key cache_key, memo_record record) -> void
|
|
84
|
+
def _clear_memo_hooks: (Symbol? hook_type) -> void
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module CacheMetricsMethods
|
|
88
|
+
@__safe_memo_metrics__: Hash[memo_key, { hits: Integer, misses: Integer, total_time: Float }]?
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def memo_metrics_store: () -> Hash[memo_key, { hits: Integer, misses: Integer, total_time: Float }]
|
|
93
|
+
def record_cache_hit: (Symbol method_name, Array[untyped] args) -> void
|
|
94
|
+
def record_cache_miss: (Symbol method_name, Array[untyped] args, Float computation_time) -> void
|
|
95
|
+
def _reset_cache_metrics: () -> void
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module PublicMetricsMethods
|
|
99
|
+
def cache_stats: () -> Hash[Symbol, untyped]
|
|
100
|
+
def cache_stats_for: (Symbol | String method_name) -> Hash[Symbol, untyped]
|
|
101
|
+
def cache_hit_rate: () -> Float
|
|
102
|
+
def cache_miss_rate: () -> Float
|
|
103
|
+
def cache_metrics_reset: () -> void
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
module CustomKeyMethods
|
|
107
|
+
@__safe_memo_custom_keys__: Hash[Symbol, Proc]?
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def custom_key_store: () -> Hash[Symbol, Proc]
|
|
112
|
+
def register_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
|
|
113
|
+
def compute_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> memo_key
|
|
114
|
+
def _clear_custom_keys: () -> void
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
module PublicCustomKeyMethods
|
|
118
|
+
def memoize_with_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
|
|
119
|
+
def clear_custom_keys: (Symbol | String? method_name) -> void
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module LruMethods
|
|
123
|
+
@__safe_memo_lru_order__: Hash[Symbol, Hash[memo_key, true]]?
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def lru_order_store: () -> Hash[Symbol, Hash[memo_key, true]]
|
|
128
|
+
def lru_touch: (Symbol method_name, memo_key cache_key) -> void
|
|
129
|
+
def lru_evict_if_over_limit: (Symbol method_name, Integer max_size) -> void
|
|
130
|
+
def lru_clear_all: () -> void
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module InstanceMethods
|
|
134
|
+
include PublicMethods
|
|
135
|
+
include CacheStoreMethods
|
|
136
|
+
include CacheRecordMethods
|
|
137
|
+
include InspectionMethods
|
|
138
|
+
include HooksMethods
|
|
139
|
+
include CacheMetricsMethods
|
|
140
|
+
include PublicMetricsMethods
|
|
141
|
+
include CustomKeyMethods
|
|
142
|
+
include PublicCustomKeyMethods
|
|
143
|
+
include LruMethods
|
|
46
144
|
end
|
|
47
145
|
end
|
|
146
|
+
|