legion-cache 1.4.2 → 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.
data/lib/legion/cache.rb CHANGED
@@ -4,7 +4,6 @@ require 'legion/logging/helper'
4
4
  require 'legion/cache/version'
5
5
  require 'legion/cache/settings'
6
6
  require 'legion/cache/cacheable'
7
-
8
7
  require 'legion/cache/memcached'
9
8
  require 'legion/cache/redis'
10
9
  require 'legion/cache/redis_hash'
@@ -26,18 +25,17 @@ module Legion
26
25
 
27
26
  class << self
28
27
  include Legion::Logging::Helper
28
+ include Legion::Cache::Pool
29
29
 
30
30
  def enabled?
31
- return true unless defined?(Legion::Settings)
32
-
33
- Legion::Settings.dig(:cache, :enabled) != false
34
- rescue StandardError => e
35
- handle_exception(e, level: :warn, handled: true, operation: :cache_enabled)
36
- true
31
+ !defined?(Legion::Settings) || Legion::Settings.dig(:cache, :enabled) != false
37
32
  end
38
33
 
39
34
  def connected?
40
- @connected&.true? || false
35
+ return Legion::Cache::Memory.connected? if using_memory?
36
+ return Legion::Cache::Local.enabled? && Legion::Cache::Local.connected? if using_local?
37
+
38
+ enabled? && @connected.value
41
39
  end
42
40
 
43
41
  def driver_name
@@ -49,65 +47,50 @@ module Legion
49
47
 
50
48
  def stats
51
49
  {
52
- driver: driver_name,
53
- servers: resolved_servers,
54
- enabled: enabled?,
55
- connected: connected?,
56
- using_local: using_local?,
57
- using_memory: using_memory?,
58
- pool_size: safe_pool_size,
59
- pool_available: safe_pool_available,
60
- async_pool_size: async_writer_pool_size,
61
- async_queue_depth: async_writer_queue_depth,
62
- async_processed: async_writer_processed_count,
63
- async_failed: async_writer_failed_count,
64
- reconnect_attempts: reconnector_attempts,
65
- uptime: uptime_seconds
50
+ driver: driver_name, servers: resolved_servers, enabled: enabled?, connected: connected?,
51
+ using_local: using_local?, using_memory: using_memory?, pool_size: pool_size, pool_available: available,
52
+ async_pool_size: async_writer.pool_size, async_queue_depth: async_writer.queue_depth,
53
+ async_processed: async_writer.processed_count, async_failed: async_writer.failed_count,
54
+ reconnect_attempts: @reconnector&.attempts || 0, uptime: @setup_at ? (Time.now - @setup_at).to_i : 0
66
55
  }.freeze
67
- rescue StandardError => e
68
- handle_exception(e, level: :warn, handled: true, operation: :cache_stats)
69
- { error: e.message }.freeze
70
56
  end
71
57
 
72
- def setup(**)
73
- return unless enabled?
74
- return Legion::Settings[:cache][:connected] = true if connected?
58
+ def setup(**opts)
59
+ return true if connected?
75
60
 
61
+ @initialized = true
62
+ @connection_options = Legion::Cache::Settings.connection_options(@connection_options || {}, opts)
76
63
  @setup_at = Time.now
77
-
78
- async_writer.start
79
-
80
64
  if ENV['LEGION_MODE'] == 'lite'
65
+ async_writer.start
81
66
  Legion::Cache::Memory.setup
82
67
  @using_memory.make_true
83
- @connected.make_true
84
- Legion::Settings[:cache][:connected] = true
85
- log.info 'Legion::Cache using in-memory adapter (lite mode)'
86
- return
68
+ @using_local.make_false
69
+ cache_mark_connected(true)
70
+ return true
87
71
  end
88
72
 
89
- log.debug { "Legion::Cache setup driver=#{Legion::Settings[:cache][:driver]} servers=#{Array(Legion::Settings[:cache][:servers]).size}" }
90
- setup_local
91
- setup_shared(**)
73
+ Legion::Cache::Local.setup
74
+ unless enabled?
75
+ result = select_local_fallback
76
+ async_writer.start if result
77
+ return result
78
+ end
79
+
80
+ async_writer.start
81
+ setup_shared
92
82
  end
93
83
 
94
84
  def shutdown
95
- log.info 'Shutting down Legion::Cache'
96
- # 1. Drain async writer FIRST (while pool is still alive)
97
85
  async_writer.stop(timeout: configured_shutdown_timeout)
98
- # 2. Stop reconnector
99
86
  stop_reconnector
100
- # 3. Now close pools
101
- if using_memory?
102
- Legion::Cache::Memory.shutdown
103
- else
104
- close unless using_local?
105
- Legion::Cache::Local.shutdown if Legion::Cache::Local.connected?
106
- end
87
+ close_existing_shared_client
88
+ Legion::Cache::Local.shutdown
89
+ Legion::Cache::Memory.shutdown if using_memory?
107
90
  @using_local.make_false
108
91
  @using_memory.make_false
109
- @connected.make_false
110
- Legion::Settings[:cache][:connected] = false
92
+ cache_mark_connected(false)
93
+ @initialized = false
111
94
  end
112
95
 
113
96
  def local
@@ -119,468 +102,349 @@ module Legion
119
102
  end
120
103
 
121
104
  def using_local?
122
- @using_local&.true? || false
105
+ @using_local.value
123
106
  end
124
107
 
125
108
  def using_memory?
126
- @using_memory&.true? || false
109
+ @using_memory.value
127
110
  end
128
111
 
129
- def client(**opts)
130
- if ENV['LEGION_MODE'] == 'lite'
131
- Legion::Cache::Memory.setup unless Legion::Cache::Memory.connected?
132
- @using_memory.make_true
133
- @using_local.make_false
134
- @connected.make_true
135
- @active_shared_driver = nil
136
- Legion::Settings[:cache][:connected] = true if defined?(Legion::Settings)
137
- return Legion::Cache::Memory.client
138
- end
112
+ def client(**)
113
+ setup(**) unless @initialized
114
+ return Legion::Cache::Memory.client if using_memory?
115
+ return Legion::Cache::Local.client if using_local?
116
+ return nil unless connected?
139
117
 
140
- configure_shared_adapter!(opts[:driver])
141
- @using_memory.make_false
142
- @using_local.make_false
143
- result = super
144
- # super (Pool) sets @connected to a plain boolean; restore AtomicBoolean
145
- @connected = Concurrent::AtomicBoolean.new(true)
146
- Legion::Settings[:cache][:connected] = true if defined?(Legion::Settings)
147
- result
148
- rescue StandardError
149
- @connected = Concurrent::AtomicBoolean.new(false)
150
- Legion::Settings[:cache][:connected] = false if defined?(Legion::Settings)
151
- raise
118
+ @client
152
119
  end
153
120
 
154
121
  def get(key)
155
- return Legion::Cache::Memory.get(key) if using_memory?
156
- return Legion::Cache::Local.get(key) if using_local?
157
- return Legion::Cache::Local.get(key) if failback_to_local?
122
+ result = cache_operation(:get, key, failure: nil)
123
+ return result unless result.nil?
158
124
 
159
- configure_shared_adapter!
160
- super
161
- rescue StandardError => e
162
- handle_exception(e, level: :warn, handled: true, operation: :cache_get, key: key)
163
- nil
125
+ local_cache_get(key)
164
126
  end
165
127
 
166
128
  def phi_max_ttl
167
- return 3600 unless defined?(Legion::Settings)
129
+ configured = defined?(Legion::Settings) ? Legion::Settings.dig(:cache, :compliance, :phi_max_ttl) : nil
130
+ return 3600 if configured.nil?
131
+ return configured if configured.is_a?(Numeric) && configured.finite? && configured.positive?
168
132
 
169
- Legion::Settings.dig(:cache, :compliance, :phi_max_ttl) || 3600
170
- rescue StandardError => e
171
- handle_exception(e, level: :warn, handled: true, operation: :cache_phi_max_ttl)
172
- 3600
133
+ raise ArgumentError, 'phi_max_ttl must be positive and finite'
173
134
  end
174
135
 
175
136
  def enforce_phi_ttl(ttl, phi: false, **)
176
137
  return ttl unless phi == true
138
+ raise ArgumentError, 'PHI ttl must be numeric and finite' unless ttl.nil? || (ttl.is_a?(Numeric) && ttl.finite?)
139
+ return phi_max_ttl if ttl.nil? || !ttl.positive?
177
140
 
178
- max = phi_max_ttl
179
- [ttl, max].min
141
+ [ttl, phi_max_ttl].min
180
142
  end
181
143
 
182
144
  def set(key, value, ttl: nil, async: true, phi: false)
183
- effective_ttl = resolve_ttl(ttl, phi: phi)
145
+ setup unless @initialized || connected?
146
+ return false unless connected?
184
147
 
185
- if async && async_writer.running?
186
- async_writer.enqueue { set_internal(key, value, ttl: effective_ttl) }
187
- true
148
+ effective_ttl = resolve_ttl(ttl, phi: phi)
149
+ if async
150
+ async_writer.enqueue(key: key) { set_sync(key, value, ttl: effective_ttl) }
188
151
  else
189
- set_internal(key, value, ttl: effective_ttl)
152
+ set_sync(key, value, ttl: effective_ttl)
190
153
  end
191
154
  end
192
155
 
193
- def set_nx(key, value, ttl: nil)
194
- effective_ttl = resolve_ttl(ttl)
195
- return Legion::Cache::Memory.set_nx(key, value, ttl: effective_ttl) if using_memory?
196
- return Legion::Cache::Local.set_nx(key, value, ttl: effective_ttl) if using_local?
197
- return Legion::Cache::Local.set_nx(key, value, ttl: effective_ttl) if failback_to_local?
198
-
199
- configure_shared_adapter!
200
- super
201
- rescue StandardError => e
202
- handle_exception(e, level: :warn, handled: true, operation: :cache_set_nx, key: key)
203
- false
156
+ def set_nx(key, value, ttl: nil, phi: false)
157
+ cache_operation(:set_nx, key, value, ttl: resolve_ttl(ttl, phi: phi), failure: false)
204
158
  end
205
159
 
206
- def set_sync(key, value, ttl: nil, **)
207
- return Legion::Cache::Memory.set_sync(key, value, ttl: ttl) if using_memory?
208
- return Legion::Cache::Local.set_sync(key, value, ttl: ttl) if using_local?
209
- return Legion::Cache::Local.set_sync(key, value, ttl: ttl) if failback_to_local?
160
+ def set_sync(key, value, ttl: nil, phi: false, **)
161
+ cache_operation(:set_sync, key, value, ttl: resolve_ttl(ttl, phi: phi), failure: false)
162
+ end
210
163
 
211
- configure_shared_adapter!
212
- super
164
+ def cas(key, ttl: nil, phi: false, &)
165
+ cache_operation(:cas, key, ttl: resolve_ttl(ttl, phi: phi), failure: false, &)
213
166
  end
214
167
 
215
- def fetch(key, ttl: nil, &)
216
- return Legion::Cache::Memory.fetch(key, ttl: ttl, &) if using_memory?
217
- return Legion::Cache::Local.fetch(key, ttl: ttl, &) if using_local?
218
- return Legion::Cache::Local.fetch(key, ttl: ttl, &) if failback_to_local?
168
+ def fetch(key, ttl: nil)
169
+ cached = get(key)
170
+ return cached unless cached.nil?
171
+ return nil unless block_given?
219
172
 
220
- configure_shared_adapter!
221
- super
173
+ value = yield
174
+ set_sync(key, value, ttl: ttl)
175
+ value
222
176
  end
223
177
 
224
178
  def delete(key, async: true)
225
- if async && async_writer.running?
226
- async_writer.enqueue { delete_internal(key) }
227
- true
179
+ setup unless @initialized || connected?
180
+ return false unless connected?
181
+
182
+ if async
183
+ async_writer.enqueue(key: key) { delete_sync(key) }
228
184
  else
229
- delete_internal(key)
185
+ delete_sync(key)
230
186
  end
231
187
  end
232
188
 
233
189
  def delete_sync(key)
234
- return Legion::Cache::Memory.delete_sync(key) if using_memory?
235
- return Legion::Cache::Local.delete_sync(key) if using_local?
236
- return Legion::Cache::Local.delete_sync(key) if failback_to_local?
237
-
238
- configure_shared_adapter!
239
- super
190
+ cache_operation(:delete_sync, key, failure: false)
240
191
  end
241
192
 
242
193
  def flush
243
- return Legion::Cache::Memory.flush if using_memory?
244
- return Legion::Cache::Local.flush if using_local?
245
- return Legion::Cache::Local.flush if failback_to_local?
246
-
247
- configure_shared_adapter!
248
- super
194
+ cache_operation(:flush, failure: false)
249
195
  end
250
196
 
251
197
  def mget(*keys)
252
198
  keys = keys.flatten
253
199
  return {} if keys.empty?
254
- return keys.to_h { |key| [key, Legion::Cache::Memory.get(key)] } if using_memory?
255
- return Legion::Cache::Local.mget(*keys) if using_local?
256
- return Legion::Cache::Local.mget(*keys) if failback_to_local?
257
200
 
258
- configure_shared_adapter!
259
- super
201
+ result = cache_operation(:mget, *keys, failure: {})
202
+ return result if using_memory? || using_local?
203
+
204
+ local_cache_mget(result, keys)
260
205
  end
261
206
 
262
- def mset(hash, ttl: nil, async: true)
263
- return true if hash.empty?
207
+ def mset(hash, ttl: nil, async: true, phi: false)
208
+ return mset_sync(hash, ttl: ttl, phi: phi) unless async
264
209
 
265
- if async && async_writer.running?
266
- async_writer.enqueue { mset_internal(hash, ttl: ttl) }
267
- true
268
- else
269
- mset_internal(hash, ttl: ttl)
210
+ setup unless @initialized || connected?
211
+ return false unless connected?
212
+
213
+ effective_ttl = resolve_ttl(ttl, phi: phi)
214
+ accepted = true
215
+ hash.each do |key, value|
216
+ result = async_writer.enqueue(key: key) { set_sync(key, value, ttl: effective_ttl) }
217
+ accepted = false unless result
270
218
  end
219
+ accepted
271
220
  end
272
221
 
273
- def mset_sync(hash, ttl: nil, **)
222
+ def mset_sync(hash, ttl: nil, phi: false, **)
274
223
  return true if hash.empty?
275
- return hash.each { |key, value| Legion::Cache::Memory.set_sync(key, value, ttl: ttl) } && true if using_memory?
276
- return Legion::Cache::Local.mset(hash, ttl: ttl) if using_local?
277
- return Legion::Cache::Local.mset(hash, ttl: ttl) if failback_to_local?
278
224
 
279
- configure_shared_adapter!
280
- super
225
+ cache_operation(:mset_sync, hash, ttl: resolve_ttl(ttl, phi: phi), failure: false)
281
226
  end
282
227
 
283
228
  def close
284
- if using_memory?
285
- Legion::Cache::Memory.shutdown
286
- @using_memory.make_false
287
- @connected.make_false
288
- Legion::Settings[:cache][:connected] = false if defined?(Legion::Settings)
289
- return false
290
- end
291
-
292
- if using_local?
293
- Legion::Cache::Local.close
294
- @using_local.make_false
295
- @connected.make_false
296
- Legion::Settings[:cache][:connected] = false if defined?(Legion::Settings)
297
- return false
298
- end
299
-
300
- return false unless instance_variable_defined?(:@client) && @client
301
-
302
- configure_shared_adapter!
303
- result = super
304
- @connected = Concurrent::AtomicBoolean.new(false)
305
- Legion::Settings[:cache][:connected] = false if defined?(Legion::Settings)
306
- result
229
+ shutdown
307
230
  end
308
231
 
309
- def restart(**opts)
310
- configure_shared_adapter!(opts[:driver])
311
- @using_memory.make_false
312
- @using_local.make_false
313
- result = super
314
- @connected = Concurrent::AtomicBoolean.new(true)
315
- Legion::Settings[:cache][:connected] = true if defined?(Legion::Settings)
316
- result
232
+ def restart(**)
233
+ shutdown
234
+ setup(**)
317
235
  end
318
236
 
319
237
  def size
320
- return Legion::Cache::Memory.size if using_memory?
321
- return Legion::Cache::Local.size if using_local?
322
-
323
- configure_shared_adapter!
324
- super
238
+ pool_size
325
239
  end
326
240
 
327
241
  def available
242
+ return 0 unless connected?
328
243
  return Legion::Cache::Memory.available if using_memory?
329
244
  return Legion::Cache::Local.available if using_local?
330
245
 
331
- configure_shared_adapter!
332
- super
246
+ @client&.available || 0
333
247
  end
334
248
 
335
249
  def pool_size
250
+ return 0 unless connected?
336
251
  return Legion::Cache::Memory.size if using_memory?
337
252
  return Legion::Cache::Local.pool_size if using_local?
338
253
 
339
- configure_shared_adapter!
340
- super
254
+ @client&.size || 0
341
255
  end
342
256
 
343
257
  def timeout
258
+ return 0 unless connected?
344
259
  return 0 if using_memory?
345
260
  return Legion::Cache::Local.timeout if using_local?
346
261
 
347
- configure_shared_adapter!
348
- super
262
+ @timeout || shared_settings[:timeout] || 5
349
263
  end
350
264
 
351
265
  private
352
266
 
353
- def async_writer
354
- Legion::Cache.instance_variable_get(:@async_writer)
355
- end
267
+ attr_reader :async_writer
268
+
269
+ def cache_operation(operation, *, failure:, **, &block)
270
+ setup unless @initialized || connected?
271
+ return failure unless connected?
356
272
 
357
- def set_internal(key, value, ttl: nil)
358
- return Legion::Cache::Memory.set(key, value, ttl: ttl) if using_memory?
359
- return Legion::Cache::Local.set(key, value, ttl: ttl) if using_local?
360
- return Legion::Cache::Local.set(key, value, ttl: ttl) if failback_to_local?
273
+ return Legion::Cache::Memory.public_send(operation, *, **, &block) if using_memory?
274
+ return Legion::Cache::Local.public_send(operation, *, **, &block) if using_local?
361
275
 
362
276
  configure_shared_adapter!
363
- set_sync(key, value, ttl: ttl)
364
- end
277
+ user_error = nil
278
+ guarded = if block
279
+ proc do |value|
280
+ block.call(value)
281
+ rescue StandardError => e
282
+ user_error = e
283
+ raise
284
+ end
285
+ end
286
+ begin
287
+ result = @shared_adapter.instance_method(operation).bind_call(self, *, **, &guarded)
288
+ rescue StandardError => e
289
+ raise if e.equal?(user_error) || !cache_connection_error?(e)
290
+
291
+ cache_backend_failed(e)
292
+ handle_exception(e, level: :warn, handled: true, operation: operation)
293
+ result = failure
294
+ end
295
+ return Legion::Cache::Local.public_send(operation, *, **, &block) if using_local? && connected?
365
296
 
366
- def delete_internal(key)
367
- return Legion::Cache::Memory.delete(key) if using_memory?
368
- return Legion::Cache::Local.delete(key) if using_local?
369
- return Legion::Cache::Local.delete(key) if failback_to_local?
297
+ connected? ? result : failure
298
+ end
370
299
 
371
- configure_shared_adapter!
372
- delete_sync(key)
300
+ def cache_mark_connected(value)
301
+ @connected.value = value
302
+ Legion::Settings[:cache][:connected] = value if defined?(Legion::Settings)
373
303
  end
374
304
 
375
- def mset_internal(hash, ttl: nil)
376
- return hash.each { |key, value| Legion::Cache::Memory.set(key, value, ttl: ttl) } && true if using_memory?
377
- return Legion::Cache::Local.mset(hash, ttl: ttl) if using_local?
378
- return Legion::Cache::Local.mset(hash, ttl: ttl) if failback_to_local?
305
+ def cache_backend_failed(error)
306
+ return unless cache_connection_error?(error)
379
307
 
380
- configure_shared_adapter!
381
- mset_sync(hash, ttl: ttl)
308
+ cache_mark_connected(false)
309
+ select_local_fallback
310
+ start_reconnector
382
311
  end
383
312
 
384
- def failback_to_local?
385
- return false unless Legion::Cache::Local.connected?
386
-
387
- setting = if defined?(Legion::Settings)
388
- Legion::Settings.dig(:cache, :failback_to_local) != false
389
- else
390
- true
391
- end
392
- setting && (!enabled? || !connected?)
393
- rescue StandardError => e
394
- handle_exception(e, level: :warn, handled: true, operation: :cache_failback_check)
395
- false
313
+ def select_local_fallback
314
+ available = local_fallback_available?
315
+ @using_local.value = available
316
+ cache_mark_connected(available)
317
+ available
396
318
  end
397
319
 
398
320
  def resolve_ttl(ttl, phi: false)
399
- effective = ttl || default_ttl
400
- enforce_phi_ttl(effective, phi: phi)
321
+ enforce_phi_ttl(ttl || shared_settings[:default_ttl] || 3600, phi: phi)
401
322
  end
402
323
 
403
- def default_ttl
404
- return 3600 unless defined?(Legion::Settings)
324
+ def local_cache_get(key)
325
+ return nil unless local_fallback_available?
405
326
 
406
- Legion::Settings.dig(:cache, :default_ttl) || 3600
407
- rescue StandardError
408
- 3600
327
+ Legion::Cache::Local.get(key)
409
328
  end
410
329
 
411
- def setup_local
412
- return if Legion::Cache::Local.connected?
330
+ def local_cache_mget(shared_values, keys)
331
+ return shared_values unless local_fallback_available?
413
332
 
414
- Legion::Cache::Local.setup
415
- rescue StandardError => e
416
- report_exception(e, level: :warn, handled: true, operation: :setup_local)
333
+ missing = keys.select { |key| shared_values[key].nil? }
334
+ return shared_values if missing.empty?
335
+
336
+ Legion::Cache::Local.mget(*missing).each do |key, value|
337
+ shared_values[key] = value unless value.nil?
338
+ end
339
+ shared_values
417
340
  end
418
341
 
419
- def setup_shared(**)
420
- client(**Legion::Settings[:cache], logger: log, **)
421
- @connected.make_true
342
+ def local_fallback_available?
343
+ shared_settings[:failback_to_local] != false &&
344
+ Legion::Cache::Local.enabled? &&
345
+ Legion::Cache::Local.connected?
346
+ end
347
+
348
+ def setup_shared
349
+ configure_shared_adapter!
350
+ settings = Legion::Cache::Settings.connection_options(shared_settings, @connection_options || {})
351
+ @shared_adapter.instance_method(:client).bind_call(self, **settings, logger: log)
422
352
  @using_local.make_false
423
- Legion::Settings[:cache][:connected] = true
424
- driver = Legion::Settings[:cache][:driver] || 'dalli'
425
- servers = Array(Legion::Settings[:cache][:servers]).join(', ')
426
- log.info "Legion::Cache connected (driver=#{driver}) to #{servers}"
353
+ @using_memory.make_false
354
+ cache_mark_connected(true)
355
+ true
427
356
  rescue StandardError => e
428
- report_exception(e, level: :warn, handled: true, operation: :setup_shared, fallback: :local)
429
- if Legion::Cache::Local.connected?
430
- @using_local.make_true
431
- @connected.make_true
432
- Legion::Settings[:cache][:connected] = true
433
- log.info 'Legion::Cache fell back to Local cache'
434
- else
435
- @connected.make_false
436
- Legion::Settings[:cache][:connected] = false
437
- log.error 'Legion::Cache shared and local adapters are unavailable'
438
- end
439
- start_reconnector if enabled?
357
+ close_existing_shared_client
358
+ raise unless cache_connection_error?(e)
359
+
360
+ handle_exception(e, level: :warn, handled: true, operation: :setup_shared)
361
+ result = select_local_fallback
362
+ start_reconnector
363
+ result
440
364
  end
441
365
 
442
366
  def reconnect_shared!
443
- client(**Legion::Settings[:cache], logger: log)
444
- @connected.make_true
445
- @using_local.make_false
446
- Legion::Settings[:cache][:connected] = true
447
- log.info 'Legion::Cache shared reconnected'
448
- end
367
+ return false unless reconnect_enabled?
368
+
369
+ close_existing_shared_client
370
+ configure_shared_adapter!
371
+ settings = Legion::Cache::Settings.connection_options(shared_settings, @connection_options || {})
372
+ @shared_adapter.instance_method(:client).bind_call(self, **settings, logger: log)
373
+ unless reconnect_enabled?
374
+ close_existing_shared_client
375
+ select_local_fallback
376
+ return false
377
+ end
449
378
 
450
- def report_exception(exception, level:, handled:, **)
451
- handle_exception(exception, level: level, handled: handled, **)
379
+ @using_local.make_false
380
+ cache_mark_connected(true)
381
+ true
382
+ rescue StandardError => e
383
+ close_existing_shared_client
384
+ select_local_fallback
385
+ raise e
452
386
  end
453
387
 
454
388
  def configure_shared_adapter!(requested_driver = nil)
455
- driver = Legion::Cache::Settings.normalize_driver(requested_driver || configured_shared_driver)
456
- return if @active_shared_driver == driver
389
+ driver = Legion::Cache::Settings.normalize_driver(requested_driver || @connection_options&.dig(:driver) || configured_shared_driver)
390
+ return if @active_shared_driver == driver && @shared_adapter
457
391
 
458
392
  close_existing_shared_client
459
- extend build_shared_adapter(driver)
393
+ @shared_adapter = build_shared_adapter(driver)
394
+ extend @shared_adapter
460
395
 
461
396
  @active_shared_driver = driver
462
- log.info "Legion::Cache selected shared adapter=#{driver}"
463
397
  end
464
398
 
465
399
  def configured_shared_driver
466
- if defined?(Legion::Settings)
467
- Legion::Cache::Settings.normalize_driver(Legion::Settings.dig(:cache, :driver) || Legion::Cache::Settings.driver)
468
- else
469
- Legion::Cache::Settings.driver
470
- end
471
- rescue StandardError => e
472
- handle_exception(e, level: :warn, handled: true, operation: :cache_configured_shared_driver)
473
- 'dalli'
400
+ Legion::Cache::Settings.normalize_driver(shared_settings[:driver] || Legion::Cache::Settings.driver)
474
401
  end
475
402
 
476
403
  def build_shared_adapter(driver)
477
- case Legion::Cache::Settings.normalize_driver(driver)
478
- when 'redis'
479
- Legion::Cache::Redis.dup
480
- else
481
- Legion::Cache::Memcached.dup
482
- end
404
+ driver == 'redis' ? Legion::Cache::Redis.dup : Legion::Cache::Memcached.dup
483
405
  end
484
406
 
485
407
  def close_existing_shared_client
486
- return unless instance_variable_defined?(:@client) && @client
487
-
488
- @client.shutdown(&:close)
408
+ pool = @client
409
+ @client = nil
410
+ pool&.shutdown(&:close)
489
411
  rescue StandardError => e
490
412
  handle_exception(e, level: :warn, handled: true, operation: :cache_close_existing_shared_client)
491
- ensure
492
- @client = nil
493
- @connected = Concurrent::AtomicBoolean.new(false)
494
413
  end
495
414
 
496
- def resolved_servers
497
- return [] if using_memory?
415
+ def shared_settings
416
+ return Legion::Cache::Settings.default unless defined?(Legion::Settings)
498
417
 
499
- Array(Legion::Settings.dig(:cache, :servers))
500
- rescue StandardError
501
- []
502
- end
503
-
504
- def safe_pool_size
505
- return 1 if using_memory?
506
- return 0 unless connected?
507
-
508
- pool_size
509
- rescue StandardError
510
- 0
418
+ Legion::Settings[:cache] || Legion::Cache::Settings.default
511
419
  end
512
420
 
513
- def safe_pool_available
514
- return 1 if using_memory?
515
- return 0 unless connected?
516
-
517
- available
518
- rescue StandardError
519
- 0
520
- end
521
-
522
- def async_writer_pool_size
523
- async_writer.pool_size
524
- rescue StandardError
525
- 0
526
- end
527
-
528
- def async_writer_queue_depth
529
- async_writer.queue_depth
530
- rescue StandardError
531
- 0
532
- end
533
-
534
- def async_writer_processed_count
535
- async_writer.processed_count
536
- rescue StandardError
537
- 0
421
+ def resolved_servers
422
+ using_memory? ? [] : Array(shared_settings[:servers])
538
423
  end
539
424
 
540
425
  def configured_shutdown_timeout
541
- return 5 unless defined?(Legion::Settings)
542
-
543
- Legion::Settings.dig(:cache, :async, :shutdown_timeout) || 5
544
- rescue StandardError
545
- 5
546
- end
547
-
548
- def async_writer_failed_count
549
- async_writer.failed_count
550
- rescue StandardError
551
- 0
426
+ shared_settings.dig(:async, :shutdown_timeout) || 5
552
427
  end
553
428
 
554
- def reconnector_attempts
555
- @reconnector&.attempts || 0
429
+ def reconnect_enabled?
430
+ enabled? && shared_settings.dig(:reconnect, :enabled) != false
556
431
  end
557
432
 
558
433
  def start_reconnector
559
- return unless enabled?
434
+ return unless reconnect_enabled?
435
+ return if @reconnector&.running?
560
436
 
561
- stop_reconnector
562
437
  @reconnector = Legion::Cache::Reconnector.new(
563
- tier: :shared,
564
- connect_block: -> { reconnect_shared! },
565
- enabled_block: -> { enabled? },
566
- settings_key: :cache
438
+ tier: :shared, connect_block: -> { reconnect_shared! },
439
+ enabled_block: -> { reconnect_enabled? }, settings_key: :cache
567
440
  )
568
441
  @reconnector.start
569
- log.info 'Legion::Cache started background reconnector for shared tier'
570
442
  end
571
443
 
572
444
  def stop_reconnector
573
445
  @reconnector&.stop
574
446
  @reconnector = nil
575
447
  end
576
-
577
- def uptime_seconds
578
- return 0 unless @setup_at
579
-
580
- (Time.now - @setup_at).to_i
581
- rescue StandardError
582
- 0
583
- end
584
448
  end
585
449
  end
586
450
  end