legion-cache 1.4.1 → 1.4.3
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/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/README.md +152 -30
- data/legion-cache.gemspec +3 -2
- data/lib/legion/cache/async_writer.rb +88 -36
- data/lib/legion/cache/cacheable.rb +37 -36
- data/lib/legion/cache/helper.rb +14 -12
- data/lib/legion/cache/local.rb +118 -109
- data/lib/legion/cache/memcached.rb +97 -23
- data/lib/legion/cache/memory.rb +222 -87
- data/lib/legion/cache/pool.rb +32 -15
- data/lib/legion/cache/reconnector.rb +50 -11
- data/lib/legion/cache/redis.rb +155 -118
- data/lib/legion/cache/redis_hash.rb +16 -13
- data/lib/legion/cache/settings.rb +8 -0
- data/lib/legion/cache/version.rb +1 -1
- data/lib/legion/cache.rb +222 -345
- metadata +35 -3
data/lib/legion/cache/helper.rb
CHANGED
|
@@ -310,10 +310,11 @@ module Legion
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
def memcached_hash_merge(full_key, new_fields)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
result = Legion::Cache.cas(full_key, ttl: cache_default_ttl) do |raw|
|
|
314
|
+
current = raw.nil? ? {} : Legion::JSON.load(raw).transform_keys(&:to_s)
|
|
315
|
+
Legion::JSON.dump(current.merge(new_fields.transform_keys(&:to_s)))
|
|
316
|
+
end
|
|
317
|
+
!!result
|
|
317
318
|
end
|
|
318
319
|
|
|
319
320
|
def memcached_hash_load(full_key)
|
|
@@ -329,14 +330,15 @@ module Legion
|
|
|
329
330
|
end
|
|
330
331
|
|
|
331
332
|
def memcached_hash_delete_fields(full_key, fields)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
333
|
+
removed = 0
|
|
334
|
+
result = Legion::Cache.cas(full_key, ttl: cache_default_ttl) do |raw|
|
|
335
|
+
current = raw.nil? ? {} : Legion::JSON.load(raw).transform_keys(&:to_s)
|
|
336
|
+
str_fields = fields.map(&:to_s).uniq
|
|
337
|
+
removed = str_fields.count { |field| current.key?(field) }
|
|
338
|
+
str_fields.each { |field| current.delete(field) }
|
|
339
|
+
Legion::JSON.dump(current)
|
|
340
|
+
end
|
|
341
|
+
result ? removed : 0
|
|
340
342
|
end
|
|
341
343
|
|
|
342
344
|
def raise_sorted_set_unsupported(method)
|
data/lib/legion/cache/local.rb
CHANGED
|
@@ -3,55 +3,58 @@
|
|
|
3
3
|
require 'concurrent'
|
|
4
4
|
require 'legion/logging/helper'
|
|
5
5
|
require 'legion/cache/settings'
|
|
6
|
+
require 'legion/cache/async_writer'
|
|
7
|
+
require 'legion/cache/pool'
|
|
6
8
|
|
|
7
9
|
module Legion
|
|
8
10
|
module Cache
|
|
9
11
|
module Local
|
|
10
12
|
@connected = Concurrent::AtomicBoolean.new(false)
|
|
13
|
+
@async_writer = Legion::Cache::AsyncWriter.new(settings_key: :cache_local)
|
|
11
14
|
|
|
12
15
|
class << self
|
|
13
16
|
include Legion::Logging::Helper
|
|
17
|
+
include Legion::Cache::Pool
|
|
14
18
|
|
|
15
|
-
def setup(**)
|
|
16
|
-
return unless enabled?
|
|
17
|
-
return if connected?
|
|
18
|
-
|
|
19
|
-
settings = local_settings
|
|
20
|
-
return unless settings[:enabled]
|
|
19
|
+
def setup(**opts)
|
|
20
|
+
return false unless enabled?
|
|
21
|
+
return true if connected?
|
|
21
22
|
|
|
23
|
+
@connection_options = Legion::Cache::Settings.connection_options(@connection_options || {}, opts)
|
|
24
|
+
settings = Legion::Cache::Settings.connection_options(local_settings, @connection_options)
|
|
22
25
|
driver_name = settings[:driver] || Legion::Cache::Settings.driver
|
|
23
26
|
@driver_name = Legion::Cache::Settings.normalize_driver(driver_name)
|
|
24
27
|
@driver = build_driver(driver_name)
|
|
25
|
-
@driver.
|
|
26
|
-
@
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
@driver.instance_variable_set(:@cache_settings, settings)
|
|
29
|
+
@driver.instance_variable_set(:@cache_failure_callback, ->(error) { cache_backend_failed(error) })
|
|
30
|
+
@driver.client(**settings, logger: log)
|
|
31
|
+
@async_writer.start
|
|
32
|
+
cache_mark_connected(true)
|
|
33
|
+
true
|
|
29
34
|
rescue StandardError => e
|
|
35
|
+
@driver&.close
|
|
36
|
+
@driver = nil
|
|
37
|
+
cache_mark_connected(false)
|
|
30
38
|
handle_exception(e, level: :warn, handled: true, operation: :cache_local_setup, driver: driver_name)
|
|
31
|
-
|
|
39
|
+
raise unless cache_connection_error?(e)
|
|
40
|
+
|
|
41
|
+
false
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def shutdown
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
log.info 'Shutting down Legion::Cache::Local'
|
|
45
|
+
@async_writer.stop
|
|
38
46
|
@driver&.close
|
|
39
47
|
@driver = nil
|
|
40
48
|
@driver_name = nil
|
|
41
|
-
|
|
49
|
+
cache_mark_connected(false)
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def enabled?
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Legion::Settings.dig(:cache_local, :enabled) != false
|
|
48
|
-
rescue StandardError => e
|
|
49
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_enabled)
|
|
50
|
-
true
|
|
53
|
+
!defined?(Legion::Settings) || Legion::Settings.dig(:cache_local, :enabled) != false
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def connected?
|
|
54
|
-
|
|
57
|
+
enabled? && @connected.value
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def driver_name
|
|
@@ -59,154 +62,160 @@ module Legion
|
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def get(key)
|
|
62
|
-
|
|
63
|
-
log.debug { "[cache:local] GET #{key} hit=#{!result.nil?}" }
|
|
64
|
-
result
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_get, key: key)
|
|
67
|
-
nil
|
|
65
|
+
local_operation(:get, key, failure: nil)
|
|
68
66
|
end
|
|
69
67
|
|
|
70
|
-
def set(key, value, ttl: nil, **)
|
|
71
|
-
|
|
68
|
+
def set(key, value, ttl: nil, async: true, phi: false, **) # rubocop:disable Metrics/ParameterLists
|
|
69
|
+
return false unless enabled? && connected?
|
|
70
|
+
|
|
71
|
+
ttl = local_write_ttl(ttl, phi: phi)
|
|
72
|
+
if async
|
|
73
|
+
@async_writer.enqueue(key: key) { set_sync(key, value, ttl: ttl) }
|
|
74
|
+
else
|
|
75
|
+
set_sync(key, value, ttl: ttl)
|
|
76
|
+
end
|
|
72
77
|
end
|
|
73
78
|
|
|
74
|
-
def set_sync(key, value, ttl: nil, **)
|
|
75
|
-
|
|
76
|
-
result = @driver.set_sync(key, value, ttl: effective_ttl)
|
|
77
|
-
log.debug { "[cache:local] SET #{key} ttl=#{effective_ttl} success=#{result}" }
|
|
78
|
-
result
|
|
79
|
-
rescue StandardError => e
|
|
80
|
-
handle_exception(e, level: :error, handled: false, operation: :cache_local_set_sync, key: key, ttl: effective_ttl)
|
|
81
|
-
raise
|
|
79
|
+
def set_sync(key, value, ttl: nil, phi: false, **)
|
|
80
|
+
local_operation(:set_sync, key, value, ttl: local_write_ttl(ttl, phi: phi), failure: false)
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
def set_nx(key, value, ttl: nil, phi: false)
|
|
84
|
+
local_operation(:set_nx, key, value, ttl: local_write_ttl(ttl, phi: phi), failure: false)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def cas(key, ttl: nil, phi: false, &)
|
|
88
|
+
local_operation(:cas, key, ttl: local_write_ttl(ttl, phi: phi), failure: false, &)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fetch(key, ttl: nil)
|
|
92
|
+
cached = get(key)
|
|
93
|
+
return cached unless cached.nil?
|
|
94
|
+
return nil unless block_given?
|
|
95
|
+
|
|
96
|
+
value = yield
|
|
97
|
+
set_sync(key, value, ttl: ttl)
|
|
98
|
+
value
|
|
91
99
|
end
|
|
92
100
|
|
|
93
|
-
def delete(key, **)
|
|
94
|
-
|
|
101
|
+
def delete(key, async: true, **)
|
|
102
|
+
return false unless enabled? && connected?
|
|
103
|
+
|
|
104
|
+
if async
|
|
105
|
+
@async_writer.enqueue(key: key) { delete_sync(key) }
|
|
106
|
+
else
|
|
107
|
+
delete_sync(key)
|
|
108
|
+
end
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
def delete_sync(key)
|
|
98
|
-
|
|
99
|
-
log.debug { "[cache:local] DELETE #{key} success=#{result}" }
|
|
100
|
-
result
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
handle_exception(e, level: :error, handled: false, operation: :cache_local_delete_sync, key: key)
|
|
103
|
-
raise
|
|
112
|
+
local_operation(:delete_sync, key, failure: false)
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def flush
|
|
107
|
-
|
|
108
|
-
log.debug { '[cache:local] FLUSH completed' }
|
|
109
|
-
result
|
|
110
|
-
rescue StandardError => e
|
|
111
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_flush)
|
|
112
|
-
nil
|
|
116
|
+
local_operation(:flush, failure: false)
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def mget(*keys)
|
|
116
|
-
|
|
117
|
-
return {} if keys.empty?
|
|
120
|
+
return {} unless enabled?
|
|
118
121
|
|
|
119
|
-
keys.to_h { |key| [key, get(key)] }
|
|
122
|
+
keys.flatten.to_h { |key| [key, get(key)] }
|
|
120
123
|
end
|
|
121
124
|
|
|
122
|
-
def mset(hash, ttl: nil, **)
|
|
123
|
-
return
|
|
125
|
+
def mset(hash, ttl: nil, async: true, phi: false, **)
|
|
126
|
+
return false unless enabled?
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
accepted = true
|
|
129
|
+
hash.each do |key, value|
|
|
130
|
+
result = set(key, value, ttl: ttl, async: async, phi: phi)
|
|
131
|
+
accepted = false unless result
|
|
132
|
+
end
|
|
133
|
+
accepted
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mset_sync(hash, ttl: nil, phi: false, **)
|
|
137
|
+
mset(hash, ttl: ttl, async: false, phi: phi)
|
|
127
138
|
end
|
|
128
139
|
|
|
129
140
|
def stats
|
|
130
|
-
{
|
|
131
|
-
driver: driver_name,
|
|
132
|
-
servers: local_servers,
|
|
133
|
-
enabled: enabled?,
|
|
134
|
-
connected: connected?
|
|
135
|
-
}.freeze
|
|
136
|
-
rescue StandardError => e
|
|
137
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_stats)
|
|
138
|
-
{ error: e.message }.freeze
|
|
141
|
+
{ driver: driver_name, servers: Array(local_settings[:servers]), enabled: enabled?, connected: connected? }.freeze
|
|
139
142
|
end
|
|
140
143
|
|
|
141
|
-
def client
|
|
142
|
-
|
|
144
|
+
def client(**)
|
|
145
|
+
return nil unless enabled?
|
|
146
|
+
|
|
147
|
+
setup(**) unless @driver
|
|
148
|
+
@driver&.client if connected?
|
|
143
149
|
end
|
|
144
150
|
|
|
145
151
|
def close
|
|
146
|
-
|
|
147
|
-
@driver = nil
|
|
148
|
-
@driver_name = nil
|
|
149
|
-
@connected.make_false
|
|
150
|
-
log.info 'Legion::Cache::Local pool closed'
|
|
151
|
-
@connected
|
|
152
|
+
shutdown
|
|
152
153
|
end
|
|
153
154
|
|
|
154
|
-
def restart(**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
@connected.make_true
|
|
158
|
-
log.info 'Legion::Cache::Local pool restarted'
|
|
159
|
-
@connected
|
|
155
|
+
def restart(**)
|
|
156
|
+
shutdown
|
|
157
|
+
setup(**)
|
|
160
158
|
end
|
|
161
159
|
|
|
162
160
|
def size
|
|
163
|
-
@driver.size
|
|
161
|
+
connected? ? @driver.size : 0
|
|
164
162
|
end
|
|
165
163
|
|
|
166
164
|
def available
|
|
167
|
-
@driver.available
|
|
165
|
+
connected? ? @driver.available : 0
|
|
168
166
|
end
|
|
169
167
|
|
|
170
168
|
def pool_size
|
|
171
|
-
@driver.pool_size
|
|
169
|
+
connected? ? @driver.pool_size : 0
|
|
172
170
|
end
|
|
173
171
|
|
|
174
172
|
def timeout
|
|
175
|
-
@driver.timeout
|
|
173
|
+
connected? ? @driver.timeout : 0
|
|
176
174
|
end
|
|
177
175
|
|
|
178
176
|
def reset!
|
|
179
|
-
|
|
180
|
-
@
|
|
181
|
-
@connected.make_false
|
|
182
|
-
log.debug 'Legion::Cache::Local state reset'
|
|
183
|
-
@connected
|
|
177
|
+
shutdown
|
|
178
|
+
@connection_options = nil
|
|
184
179
|
end
|
|
185
180
|
|
|
186
181
|
private
|
|
187
182
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
def local_operation(operation, *, failure:, **, &block)
|
|
184
|
+
return failure unless enabled? && connected? && @driver
|
|
185
|
+
|
|
186
|
+
user_error = nil
|
|
187
|
+
guarded = if block
|
|
188
|
+
proc do |value|
|
|
189
|
+
block.call(value)
|
|
190
|
+
rescue StandardError => e
|
|
191
|
+
user_error = e
|
|
192
|
+
raise
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
@driver.public_send(operation, *, **, &guarded)
|
|
196
|
+
rescue StandardError => e
|
|
197
|
+
raise if e.equal?(user_error) || !cache_connection_error?(e)
|
|
198
|
+
|
|
199
|
+
cache_backend_failed(e)
|
|
200
|
+
handle_exception(e, level: :warn, handled: true, operation: operation)
|
|
201
|
+
failure
|
|
192
202
|
end
|
|
193
203
|
|
|
194
|
-
def
|
|
195
|
-
|
|
204
|
+
def local_write_ttl(ttl, phi:)
|
|
205
|
+
effective = ttl || local_settings[:default_ttl] || 21_600
|
|
206
|
+
return effective unless phi
|
|
196
207
|
|
|
197
|
-
Legion::
|
|
198
|
-
rescue StandardError
|
|
199
|
-
21_600
|
|
208
|
+
Legion::Cache.enforce_phi_ttl(effective, phi: phi)
|
|
200
209
|
end
|
|
201
210
|
|
|
202
211
|
def build_driver(driver_name)
|
|
203
212
|
case Legion::Cache::Settings.normalize_driver(driver_name)
|
|
204
213
|
when 'redis'
|
|
205
214
|
require 'legion/cache/redis'
|
|
206
|
-
Legion::Cache::Redis
|
|
215
|
+
Object.new.extend(Legion::Cache::Redis, Legion::Logging::Helper)
|
|
207
216
|
else
|
|
208
217
|
require 'legion/cache/memcached'
|
|
209
|
-
Legion::Cache::Memcached
|
|
218
|
+
Object.new.extend(Legion::Cache::Memcached, Legion::Logging::Helper)
|
|
210
219
|
end
|
|
211
220
|
end
|
|
212
221
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
|
+
require 'timeout'
|
|
4
5
|
require 'dalli'
|
|
5
6
|
require 'legion/logging/helper'
|
|
6
7
|
require 'legion/cache/pool'
|
|
@@ -16,12 +17,17 @@ module Legion
|
|
|
16
17
|
username: nil, password: nil, logger: nil, **opts)
|
|
17
18
|
return @client unless @client.nil?
|
|
18
19
|
|
|
19
|
-
settings = defined?(Legion::Settings) ? Legion::Settings[:cache] : {}
|
|
20
|
-
|
|
20
|
+
settings = @cache_settings || (defined?(Legion::Settings) ? Legion::Settings[:cache] : {})
|
|
21
|
+
if server.nil? && servers.nil?
|
|
22
|
+
server = settings[:server]
|
|
23
|
+
servers = settings[:servers]
|
|
24
|
+
end
|
|
21
25
|
@component_logger = logger || log
|
|
22
26
|
|
|
23
27
|
@pool_size = pool_size || settings[:pool_size] || 10
|
|
24
28
|
@timeout = timeout || settings[:timeout] || 5
|
|
29
|
+
health_timeout = Float(@timeout)
|
|
30
|
+
raise ArgumentError, 'memcached timeout must be finite and positive' unless health_timeout.finite? && health_timeout.positive?
|
|
25
31
|
|
|
26
32
|
resolved = Legion::Cache::Settings.resolve_servers(
|
|
27
33
|
driver: 'memcached', server: server, servers: Array(servers)
|
|
@@ -33,22 +39,29 @@ module Legion
|
|
|
33
39
|
cache_opts[:serializer] ||= Legion::JSON
|
|
34
40
|
cache_opts[:username] = username unless username.nil?
|
|
35
41
|
cache_opts[:password] = password unless password.nil?
|
|
42
|
+
cache_opts[:socket_timeout] ||= health_timeout
|
|
43
|
+
@memcached_settings = cache_opts
|
|
36
44
|
|
|
37
45
|
tls_ctx = memcached_tls_context(port: resolved.first.split(':').last.to_i)
|
|
38
|
-
cache_opts[:ssl_context]
|
|
46
|
+
cache_opts[:ssl_context] ||= tls_ctx if tls_ctx
|
|
39
47
|
|
|
40
48
|
checkout_timeout = opts[:pool_checkout_timeout] || settings[:pool_checkout_timeout] || @timeout
|
|
41
|
-
|
|
49
|
+
pool = ConnectionPool.new(size: @pool_size, timeout: checkout_timeout) do
|
|
42
50
|
Dalli::Client.new(resolved, cache_opts)
|
|
43
51
|
end
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
Timeout.timeout(health_timeout) { pool.with(&:alive!) }
|
|
54
|
+
@client = pool
|
|
55
|
+
cache_mark_connected(true)
|
|
46
56
|
log.info "Memcached connected to #{resolved.join(', ')}"
|
|
47
57
|
@client
|
|
48
58
|
rescue StandardError => e
|
|
49
59
|
handle_exception(e, level: :error, handled: false, operation: :memcached_client,
|
|
50
60
|
server: server, servers: Array(servers))
|
|
51
|
-
|
|
61
|
+
cache_mark_connected(false)
|
|
62
|
+
cache_backend_failed(e)
|
|
63
|
+
memcached_discard_pool(pool)
|
|
64
|
+
@client = nil
|
|
52
65
|
raise
|
|
53
66
|
end
|
|
54
67
|
|
|
@@ -57,23 +70,52 @@ module Legion
|
|
|
57
70
|
log.debug { "[cache] GET #{key} hit=#{!result.nil?}" }
|
|
58
71
|
result
|
|
59
72
|
rescue StandardError => e
|
|
73
|
+
cache_backend_failed(e)
|
|
60
74
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_get, key: key)
|
|
61
75
|
nil
|
|
62
76
|
end
|
|
63
77
|
|
|
64
|
-
def fetch(key, ttl: nil
|
|
65
|
-
result =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
def fetch(key, ttl: nil)
|
|
79
|
+
result = get(key)
|
|
80
|
+
return result unless result.nil? && block_given?
|
|
81
|
+
|
|
82
|
+
result = yield
|
|
83
|
+
begin
|
|
84
|
+
set_nx(key, result, ttl: ttl)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_fetch, key: key, ttl: ttl)
|
|
71
87
|
end
|
|
72
|
-
log.debug { "[cache] FETCH #{key} hit=#{!result.nil?}" }
|
|
73
88
|
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def cas(key, ttl: nil)
|
|
92
|
+
raise ArgumentError, 'a block is required' unless block_given?
|
|
93
|
+
|
|
94
|
+
effective_ttl = ttl || default_ttl
|
|
95
|
+
20.times do
|
|
96
|
+
value, token = memcached_cas_request(key) { |conn| conn.get_cas(key) }
|
|
97
|
+
updated = yield value
|
|
98
|
+
committed = memcached_cas_request(key) do |conn|
|
|
99
|
+
if token.nil?
|
|
100
|
+
conn.add(key, updated, effective_ttl)
|
|
101
|
+
else
|
|
102
|
+
conn.set_cas(key, updated, token, effective_ttl)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
return updated if committed
|
|
106
|
+
end
|
|
107
|
+
raise 'Memcached CAS contention exceeded the retry limit'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def set_nx(key, value, ttl: nil)
|
|
111
|
+
effective_ttl = ttl || default_ttl
|
|
112
|
+
result = client.with { |conn| conn.add(key, value, effective_ttl) }
|
|
113
|
+
log.debug { "[cache] SET_NX #{key} ttl=#{effective_ttl.inspect} result=#{result}" }
|
|
114
|
+
!!result
|
|
74
115
|
rescue StandardError => e
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
cache_backend_failed(e)
|
|
117
|
+
handle_exception(e, level: :error, handled: false, operation: :memcached_set_nx, key: key, ttl: effective_ttl)
|
|
118
|
+
raise
|
|
77
119
|
end
|
|
78
120
|
|
|
79
121
|
def set(key, value, ttl: nil, **)
|
|
@@ -82,10 +124,11 @@ module Legion
|
|
|
82
124
|
|
|
83
125
|
def set_sync(key, value, ttl: nil, **)
|
|
84
126
|
effective_ttl = ttl || default_ttl
|
|
85
|
-
result = client.with { |conn| conn.set(key, value, effective_ttl)
|
|
127
|
+
result = client.with { |conn| conn.set(key, value, effective_ttl) }
|
|
86
128
|
log.debug { "[cache] SET #{key} ttl=#{effective_ttl} success=#{result} value_class=#{value.class}" }
|
|
87
|
-
result
|
|
129
|
+
!!result
|
|
88
130
|
rescue StandardError => e
|
|
131
|
+
cache_backend_failed(e)
|
|
89
132
|
handle_exception(e, level: :error, handled: false, operation: :memcached_set_sync, key: key, ttl: effective_ttl)
|
|
90
133
|
raise
|
|
91
134
|
end
|
|
@@ -99,6 +142,7 @@ module Legion
|
|
|
99
142
|
log.debug { "[cache] DELETE #{key} success=#{result}" }
|
|
100
143
|
result
|
|
101
144
|
rescue StandardError => e
|
|
145
|
+
cache_backend_failed(e)
|
|
102
146
|
handle_exception(e, level: :error, handled: false, operation: :memcached_delete_sync, key: key)
|
|
103
147
|
raise
|
|
104
148
|
end
|
|
@@ -108,6 +152,7 @@ module Legion
|
|
|
108
152
|
log.debug { '[cache] FLUSH completed' }
|
|
109
153
|
result
|
|
110
154
|
rescue StandardError => e
|
|
155
|
+
cache_backend_failed(e)
|
|
111
156
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_flush)
|
|
112
157
|
nil
|
|
113
158
|
end
|
|
@@ -120,6 +165,7 @@ module Legion
|
|
|
120
165
|
log.debug { "[cache] MGET keys=#{keys.size} hits=#{result.size}" }
|
|
121
166
|
result
|
|
122
167
|
rescue StandardError => e
|
|
168
|
+
cache_backend_failed(e)
|
|
123
169
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_mget, key_count: keys.size)
|
|
124
170
|
{}
|
|
125
171
|
end
|
|
@@ -128,24 +174,49 @@ module Legion
|
|
|
128
174
|
mset_sync(hash, ttl: ttl)
|
|
129
175
|
end
|
|
130
176
|
|
|
131
|
-
def mset_sync(hash, ttl: nil, **)
|
|
177
|
+
def mset_sync(hash, ttl: nil, **)
|
|
132
178
|
return true if hash.empty?
|
|
133
179
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
180
|
+
effective_ttl = ttl || default_ttl
|
|
181
|
+
# Dalli 3 has no set_multi; newer quiet batches do not acknowledge each write.
|
|
182
|
+
result = client.with do |conn|
|
|
183
|
+
success = true
|
|
184
|
+
hash.each { |key, value| success = false unless conn.set(key, value, effective_ttl) }
|
|
185
|
+
success
|
|
186
|
+
end
|
|
187
|
+
log.debug { "[cache] MSET keys=#{hash.size} success=#{result}" }
|
|
188
|
+
result
|
|
137
189
|
rescue StandardError => e
|
|
190
|
+
cache_backend_failed(e)
|
|
138
191
|
handle_exception(e, level: :error, handled: false, operation: :memcached_mset_sync, key_count: hash.size)
|
|
139
192
|
raise
|
|
140
193
|
end
|
|
141
194
|
|
|
142
195
|
private
|
|
143
196
|
|
|
197
|
+
def memcached_discard_pool(pool)
|
|
198
|
+
pool&.shutdown(&:close)
|
|
199
|
+
rescue StandardError => e
|
|
200
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_close_failed_pool)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def memcached_cas_request(key, &)
|
|
204
|
+
client.with(&)
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
cache_backend_failed(e)
|
|
207
|
+
handle_exception(e, level: :error, handled: false, operation: :memcached_cas, key: key)
|
|
208
|
+
raise
|
|
209
|
+
end
|
|
210
|
+
|
|
144
211
|
def default_ttl
|
|
212
|
+
return @memcached_settings[:default_ttl] || 3600 if @memcached_settings
|
|
213
|
+
return @cache_settings[:default_ttl] || 3600 if @cache_settings
|
|
214
|
+
|
|
145
215
|
return 3600 unless defined?(Legion::Settings)
|
|
146
216
|
|
|
147
217
|
Legion::Settings.dig(:cache, :default_ttl) || 3600
|
|
148
|
-
rescue StandardError
|
|
218
|
+
rescue StandardError => e
|
|
219
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_default_ttl)
|
|
149
220
|
3600
|
|
150
221
|
end
|
|
151
222
|
|
|
@@ -162,6 +233,9 @@ module Legion
|
|
|
162
233
|
end
|
|
163
234
|
|
|
164
235
|
def memcached_tls_settings
|
|
236
|
+
return @memcached_settings[:tls] || {} if @memcached_settings
|
|
237
|
+
return @cache_settings[:tls] || {} if @cache_settings
|
|
238
|
+
|
|
165
239
|
return {} unless defined?(Legion::Settings)
|
|
166
240
|
|
|
167
241
|
Legion::Settings[:cache][:tls] || {}
|