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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/safe_memoize/class_methods.rb', line 5
def memoize(method_name, ttl: nil, max_size: nil, if: nil, unless: nil)
method_name = method_name.to_sym
visibility = memoized_method_visibility(method_name)
cond_if = binding.local_variable_get(:if)
cond_unless = binding.local_variable_get(:unless)
ttl = if ttl.nil?
nil
else
ttl = Float(ttl)
raise ArgumentError, "ttl must be non-negative" if ttl < 0
ttl
end
max_size = if max_size.nil?
nil
else
raise ArgumentError, "max_size must be a positive integer" unless max_size.is_a?(Integer)
raise ArgumentError, "max_size must be positive" unless max_size > 0
max_size
end
if cond_if && cond_unless
raise ArgumentError, "cannot specify both :if and :unless"
end
raise ArgumentError, ":if must be callable" if cond_if && !cond_if.respond_to?(:call)
raise ArgumentError, ":unless must be callable" if cond_unless && !cond_unless.respond_to?(:call)
condition = if cond_if
cond_if
elsif cond_unless
->(result) { !cond_unless.call(result) }
end
expires_at = ttl && Process.clock_gettime(Process::CLOCK_MONOTONIC) + ttl
mod = Module.new do
define_method(method_name) do |*args, **kwargs, &block|
return super(*args, **kwargs, &block) if block
cache_key = compute_cache_key(method_name, args, kwargs)
if max_size || condition
memo_mutex!.synchronize do
record = memo_cache_record(cache_key)
if record
lru_touch(method_name, cache_key) if max_size
record_cache_hit(method_name, args)
call_memo_hooks(:on_hit, cache_key, record)
memo_record_value(record)
else
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
value = super(*args, **kwargs)
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
if !condition || condition.call(value)
lru_evict_if_over_limit(method_name, max_size) if max_size
@__safe_memo_cache__ ||= {}
@__safe_memo_cache__[cache_key] = memo_record(value, expires_at: expires_at)
lru_touch(method_name, cache_key) if max_size
end
record_cache_miss(method_name, args, elapsed_time)
value
end
end
else
if (record = memo_cache_record(cache_key))
record_cache_hit(method_name, args)
call_memo_hooks(:on_hit, cache_key, record)
return memo_record_value(record)
end
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
result = memo_fetch_or_store(cache_key, expires_at: expires_at) { super(*args, **kwargs) }
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
with_memo_lock do
record_cache_miss(method_name, args, elapsed_time)
end
result
end
end
send(visibility, method_name)
end
prepend mod
end
|