legion-cache 1.3.22 → 1.4.1
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 +55 -0
- data/legion-cache.gemspec +1 -0
- data/lib/legion/cache/async_writer.rb +126 -0
- data/lib/legion/cache/cacheable.rb +4 -4
- data/lib/legion/cache/helper.rb +11 -11
- data/lib/legion/cache/local.rb +91 -29
- data/lib/legion/cache/memcached.rb +53 -40
- data/lib/legion/cache/memory.rb +107 -19
- data/lib/legion/cache/pool.rb +4 -1
- data/lib/legion/cache/reconnector.rb +108 -0
- data/lib/legion/cache/redis.rb +108 -47
- data/lib/legion/cache/redis_hash.rb +8 -8
- data/lib/legion/cache/settings.rb +52 -39
- data/lib/legion/cache/version.rb +1 -1
- data/lib/legion/cache.rb +310 -70
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fe912c13fe0c2a7f697df703795eaadde5865c91aa96f112b89720fa4f6d4ee
|
|
4
|
+
data.tar.gz: 81053355f02e0763186aa3bf4c66f8bbc3fe5fd5592a4349164709e567055d28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58018a8630317f2f20ad6487e44dc072965e5d5c4125a003227f1bc54d5e97a6a8eeb71c7ab12f0a014646b2ba662492019960b25e6b66d52049215f9eade7c0
|
|
7
|
+
data.tar.gz: e1cfa791e292d2448ac3ad4026c847150dc62437a1bcb3d7bf25000138b65f7e33ae82d76b5f539fe19f7ee645ef40e4ea05f2933e8ead15dc18154de1cf0967
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.4.1] - 2026-04-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- AsyncWriter TOCTOU race condition in enqueue (capture local executor reference)
|
|
9
|
+
- Reconnector deadlock on stop (release mutex before thread.join)
|
|
10
|
+
- Reconnector NoMethodError on successful reconnect (AtomicFixnum reset)
|
|
11
|
+
- Missing `require 'concurrent'` in reconnector.rb
|
|
12
|
+
- Redis cluster flush now passes auth/TLS credentials to per-node connections
|
|
13
|
+
- Async writer drains before pool close on shutdown
|
|
14
|
+
- Serialization applied to mget/mset_sync (was only on set_sync/get)
|
|
15
|
+
- Binary encoding forced before serialization prefix checks
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Automatic failback to Local tier when shared cache is disabled or disconnected (configurable via `failback_to_local: true`)
|
|
19
|
+
- `mget`/`mset` methods on Memory adapter for interface consistency
|
|
20
|
+
- Public `pool` accessor on `Legion::Cache` (replaces direct `@client` access)
|
|
21
|
+
- `failed_count` counter in AsyncWriter stats (`async_failed` in stats hash)
|
|
22
|
+
- `reconnect_shared!` raising method for reconnector connect_block
|
|
23
|
+
- End-to-end lifecycle integration test (shared fail -> local failback -> reconnect)
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Helper and Cacheable use `async: false` for read-after-write consistency
|
|
27
|
+
- AsyncWriter and Reconnector are tier-aware (`settings_key:` parameter, `:cache_local` for local tier)
|
|
28
|
+
- Redis driver `pool_size` resolved from settings (was hardcoded to 20)
|
|
29
|
+
- Pool checkout timeout separated from operation timeout (new `pool_checkout_timeout` setting)
|
|
30
|
+
- Reconnector starts on any shared failure (even when local fallback succeeds)
|
|
31
|
+
- `setup` guarded by `enabled?` check
|
|
32
|
+
- State flags (`@connected`, `@using_local`, `@using_memory`) refactored to `Concurrent::AtomicBoolean`
|
|
33
|
+
- Reconnector `@stop_signal` refactored to `Concurrent::AtomicBoolean`
|
|
34
|
+
- `RedisHash` uses public `pool` accessor instead of `instance_variable_get(:@client)`
|
|
35
|
+
|
|
36
|
+
## [1.4.0] - 2026-04-06
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- Async write support via `Legion::Cache::AsyncWriter` backed by `concurrent-ruby` ThreadPoolExecutor
|
|
40
|
+
- `set`, `delete`, and `mset` now accept `async:` keyword (default `true`) for non-blocking writes
|
|
41
|
+
- `Legion::Cache::Reconnector` with exponential backoff (1s to 60s) for background reconnection
|
|
42
|
+
- Reconnector auto-starts when both shared and local cache are unavailable at setup
|
|
43
|
+
- `enabled?` guard on both shared and local tiers
|
|
44
|
+
- `stats` method returning frozen Hash with driver, connection, pool, async, and reconnect metrics
|
|
45
|
+
- `set_sync`, `delete_sync`, `mset_sync` explicit synchronous write methods on all tiers
|
|
46
|
+
- Async and reconnect default settings in `Legion::Cache::Settings`
|
|
47
|
+
- Transparent JSON serialization for Redis driver (prefix-byte protocol, backward-compatible with legacy data)
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- All cache drivers now use keyword TTL (`ttl:`) instead of positional arguments
|
|
51
|
+
- `flush` takes no arguments across all drivers (was `flush(delay = 0)`)
|
|
52
|
+
- `Helper` module updated: `FALLBACK_TTL` changed from 60 to 3600, all delegations use keyword signatures, `cache_set`/`cache_delete`/`cache_mset` accept `async:` keyword
|
|
53
|
+
- `Cacheable` module updated: `cache_write` and `local_cache_write` use keyword TTL
|
|
54
|
+
- Default TTL changed from 60 to 3600 (shared) and 21600 (local)
|
|
55
|
+
- Version bump from 1.3.22 to 1.4.0
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- Unified exception handling model: reads return nil (handled), sync writes re-raise, lifecycle handles internally
|
|
59
|
+
|
|
5
60
|
## [1.3.22] - 2026-04-03
|
|
6
61
|
|
|
7
62
|
### Fixed
|
data/legion-cache.gemspec
CHANGED
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
'rubygems_mfa_required' => 'true'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
spec.add_dependency 'concurrent-ruby', '>= 1.2'
|
|
29
30
|
spec.add_dependency 'connection_pool', '>= 2.4'
|
|
30
31
|
spec.add_dependency 'dalli', '>= 3.0'
|
|
31
32
|
spec.add_dependency 'legion-logging', '>= 1.5.0'
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent-ruby'
|
|
4
|
+
require 'legion/logging/helper'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Cache
|
|
8
|
+
class AsyncWriter
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
11
|
+
DEFAULT_POOL_SIZE = 4
|
|
12
|
+
DEFAULT_QUEUE_SIZE = 1000
|
|
13
|
+
DEFAULT_SHUTDOWN_TIMEOUT = 5
|
|
14
|
+
|
|
15
|
+
def initialize(pool_size: nil, queue_size: nil, shutdown_timeout: nil, settings_key: :cache)
|
|
16
|
+
@settings_key = settings_key
|
|
17
|
+
@config_pool_size = pool_size
|
|
18
|
+
@config_queue_size = queue_size
|
|
19
|
+
@config_shutdown_timeout = shutdown_timeout
|
|
20
|
+
@processed = Concurrent::AtomicFixnum.new(0)
|
|
21
|
+
@failed = Concurrent::AtomicFixnum.new(0)
|
|
22
|
+
@executor = nil
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start(pool_size: nil, queue_size: nil, **)
|
|
27
|
+
@mutex.synchronize do
|
|
28
|
+
return if running?
|
|
29
|
+
|
|
30
|
+
ps = pool_size || @config_pool_size || configured_pool_size
|
|
31
|
+
qs = queue_size || @config_queue_size || configured_queue_size
|
|
32
|
+
|
|
33
|
+
@executor = Concurrent::ThreadPoolExecutor.new(
|
|
34
|
+
min_threads: 1,
|
|
35
|
+
max_threads: ps,
|
|
36
|
+
max_queue: qs,
|
|
37
|
+
fallback_policy: :caller_runs
|
|
38
|
+
)
|
|
39
|
+
log.info "Legion::Cache::AsyncWriter started pool_size=#{ps} queue_size=#{qs}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stop(timeout: nil)
|
|
44
|
+
@mutex.synchronize do
|
|
45
|
+
return unless @executor
|
|
46
|
+
|
|
47
|
+
to = timeout || @config_shutdown_timeout || configured_shutdown_timeout
|
|
48
|
+
@executor.shutdown
|
|
49
|
+
unless @executor.wait_for_termination(to)
|
|
50
|
+
@executor.kill
|
|
51
|
+
log.warn "Legion::Cache::AsyncWriter force-killed after #{to}s timeout"
|
|
52
|
+
end
|
|
53
|
+
log.info "Legion::Cache::AsyncWriter stopped processed=#{@processed.value}"
|
|
54
|
+
@executor = nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def enqueue(&block)
|
|
59
|
+
executor = @executor
|
|
60
|
+
if executor&.running?
|
|
61
|
+
executor.post do
|
|
62
|
+
block.call
|
|
63
|
+
@processed.increment
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
handle_exception(e, level: :warn, handled: true, operation: :async_writer_job)
|
|
66
|
+
@failed.increment
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
begin
|
|
70
|
+
block.call
|
|
71
|
+
@processed.increment
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
handle_exception(e, level: :warn, handled: true, operation: :async_writer_sync_fallback)
|
|
74
|
+
@failed.increment
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def running?
|
|
80
|
+
@executor&.running? == true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def pool_size
|
|
84
|
+
@executor&.max_length || 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def queue_depth
|
|
88
|
+
@executor&.queue_length || 0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def processed_count
|
|
92
|
+
@processed.value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def failed_count
|
|
96
|
+
@failed.value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def configured_pool_size
|
|
102
|
+
return DEFAULT_POOL_SIZE unless defined?(Legion::Settings)
|
|
103
|
+
|
|
104
|
+
Legion::Settings.dig(@settings_key, :async, :pool_size) || DEFAULT_POOL_SIZE
|
|
105
|
+
rescue StandardError
|
|
106
|
+
DEFAULT_POOL_SIZE
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def configured_queue_size
|
|
110
|
+
return DEFAULT_QUEUE_SIZE unless defined?(Legion::Settings)
|
|
111
|
+
|
|
112
|
+
Legion::Settings.dig(@settings_key, :async, :queue_size) || DEFAULT_QUEUE_SIZE
|
|
113
|
+
rescue StandardError
|
|
114
|
+
DEFAULT_QUEUE_SIZE
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def configured_shutdown_timeout
|
|
118
|
+
return DEFAULT_SHUTDOWN_TIMEOUT unless defined?(Legion::Settings)
|
|
119
|
+
|
|
120
|
+
Legion::Settings.dig(@settings_key, :async, :shutdown_timeout) || DEFAULT_SHUTDOWN_TIMEOUT
|
|
121
|
+
rescue StandardError
|
|
122
|
+
DEFAULT_SHUTDOWN_TIMEOUT
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -72,13 +72,13 @@ module Legion
|
|
|
72
72
|
case scope
|
|
73
73
|
when :global
|
|
74
74
|
if global_cache_available?
|
|
75
|
-
Legion::Cache.set(key, value, ttl)
|
|
75
|
+
Legion::Cache.set(key, value, ttl: ttl, async: false)
|
|
76
76
|
else
|
|
77
77
|
memory_write(key, value, ttl)
|
|
78
78
|
end
|
|
79
79
|
else
|
|
80
80
|
if local_cache_available?
|
|
81
|
-
result = local_cache_write(key, value, ttl)
|
|
81
|
+
result = local_cache_write(key, value, ttl: ttl)
|
|
82
82
|
memory_write(key, value, ttl) unless result
|
|
83
83
|
else
|
|
84
84
|
memory_write(key, value, ttl)
|
|
@@ -104,10 +104,10 @@ module Legion
|
|
|
104
104
|
LOCAL_CACHE_MISS
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def self.local_cache_write(key, value, ttl)
|
|
107
|
+
def self.local_cache_write(key, value, ttl:)
|
|
108
108
|
return unless local_cache_available?
|
|
109
109
|
|
|
110
|
-
Legion::Cache::Local.set(key, value, ttl)
|
|
110
|
+
Legion::Cache::Local.set(key, value, ttl: ttl, async: false)
|
|
111
111
|
rescue StandardError => e
|
|
112
112
|
handle_exception(e, level: :warn, operation: :local_cache_write, key: key, ttl: ttl)
|
|
113
113
|
nil
|
data/lib/legion/cache/helper.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Legion
|
|
|
7
7
|
module Helper
|
|
8
8
|
include Legion::Logging::Helper
|
|
9
9
|
|
|
10
|
-
FALLBACK_TTL =
|
|
10
|
+
FALLBACK_TTL = 3600
|
|
11
11
|
|
|
12
12
|
# --- TTL Resolution ---
|
|
13
13
|
# Override in your LEX to set a custom default TTL for the extension.
|
|
@@ -40,7 +40,7 @@ module Legion
|
|
|
40
40
|
|
|
41
41
|
def cache_set(key, value, ttl: nil, phi: false)
|
|
42
42
|
effective_ttl = ttl || cache_default_ttl
|
|
43
|
-
Legion::Cache.set(cache_namespace + key, value, effective_ttl, phi: phi)
|
|
43
|
+
Legion::Cache.set(cache_namespace + key, value, ttl: effective_ttl, async: false, phi: phi)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def cache_get(key)
|
|
@@ -48,12 +48,12 @@ module Legion
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def cache_delete(key)
|
|
51
|
-
Legion::Cache.delete(cache_namespace + key)
|
|
51
|
+
Legion::Cache.delete(cache_namespace + key, async: false)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def cache_fetch(key, ttl: nil, &)
|
|
55
55
|
effective_ttl = ttl || cache_default_ttl
|
|
56
|
-
Legion::Cache.fetch(cache_namespace + key, effective_ttl, &)
|
|
56
|
+
Legion::Cache.fetch(cache_namespace + key, ttl: effective_ttl, &)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def cache_exist?(key)
|
|
@@ -90,7 +90,7 @@ module Legion
|
|
|
90
90
|
|
|
91
91
|
effective_ttl = ttl || cache_default_ttl
|
|
92
92
|
|
|
93
|
-
hash.each { |k, v| Legion::Cache.set(cache_namespace + k, v, effective_ttl) }
|
|
93
|
+
hash.each { |k, v| Legion::Cache.set(cache_namespace + k, v, ttl: effective_ttl, async: false) }
|
|
94
94
|
true
|
|
95
95
|
rescue StandardError => e
|
|
96
96
|
log_cache_error('cache_mset', e)
|
|
@@ -114,7 +114,7 @@ module Legion
|
|
|
114
114
|
|
|
115
115
|
effective_ttl = ttl || local_cache_default_ttl
|
|
116
116
|
|
|
117
|
-
hash.each { |k, v| Legion::Cache::Local.set(cache_namespace + k, v, effective_ttl) }
|
|
117
|
+
hash.each { |k, v| Legion::Cache::Local.set(cache_namespace + k, v, ttl: effective_ttl, async: false) }
|
|
118
118
|
true
|
|
119
119
|
rescue StandardError => e
|
|
120
120
|
log_cache_error('local_cache_mset', e)
|
|
@@ -205,7 +205,7 @@ module Legion
|
|
|
205
205
|
def local_cache_set(key, value, ttl: nil, phi: false)
|
|
206
206
|
effective_ttl = ttl || local_cache_default_ttl
|
|
207
207
|
effective_ttl = Legion::Cache.enforce_phi_ttl(effective_ttl, phi: phi)
|
|
208
|
-
Legion::Cache::Local.set(cache_namespace + key, value, effective_ttl)
|
|
208
|
+
Legion::Cache::Local.set(cache_namespace + key, value, ttl: effective_ttl, async: false)
|
|
209
209
|
end
|
|
210
210
|
|
|
211
211
|
def local_cache_get(key)
|
|
@@ -213,12 +213,12 @@ module Legion
|
|
|
213
213
|
end
|
|
214
214
|
|
|
215
215
|
def local_cache_delete(key)
|
|
216
|
-
Legion::Cache::Local.delete(cache_namespace + key)
|
|
216
|
+
Legion::Cache::Local.delete(cache_namespace + key, async: false)
|
|
217
217
|
end
|
|
218
218
|
|
|
219
219
|
def local_cache_fetch(key, ttl: nil, &)
|
|
220
220
|
effective_ttl = ttl || local_cache_default_ttl
|
|
221
|
-
Legion::Cache::Local.fetch(cache_namespace + key, effective_ttl, &)
|
|
221
|
+
Legion::Cache::Local.fetch(cache_namespace + key, ttl: effective_ttl, &)
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
def local_cache_exist?(key)
|
|
@@ -312,7 +312,7 @@ module Legion
|
|
|
312
312
|
def memcached_hash_merge(full_key, new_fields)
|
|
313
313
|
current = memcached_hash_load(full_key) || {}
|
|
314
314
|
merged = current.merge(new_fields.transform_keys(&:to_s))
|
|
315
|
-
Legion::Cache.set(full_key, Legion::JSON.dump(merged), cache_default_ttl)
|
|
315
|
+
Legion::Cache.set(full_key, Legion::JSON.dump(merged), ttl: cache_default_ttl, async: false)
|
|
316
316
|
true
|
|
317
317
|
end
|
|
318
318
|
|
|
@@ -335,7 +335,7 @@ module Legion
|
|
|
335
335
|
str_fields = fields.map(&:to_s)
|
|
336
336
|
removed = str_fields.count { |f| current.key?(f) }
|
|
337
337
|
str_fields.each { |f| current.delete(f) }
|
|
338
|
-
Legion::Cache.set(full_key, Legion::JSON.dump(current), cache_default_ttl)
|
|
338
|
+
Legion::Cache.set(full_key, Legion::JSON.dump(current), ttl: cache_default_ttl, async: false)
|
|
339
339
|
removed
|
|
340
340
|
end
|
|
341
341
|
|
data/lib/legion/cache/local.rb
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'concurrent'
|
|
3
4
|
require 'legion/logging/helper'
|
|
4
5
|
require 'legion/cache/settings'
|
|
5
6
|
|
|
6
7
|
module Legion
|
|
7
8
|
module Cache
|
|
8
9
|
module Local
|
|
10
|
+
@connected = Concurrent::AtomicBoolean.new(false)
|
|
11
|
+
|
|
9
12
|
class << self
|
|
10
13
|
include Legion::Logging::Helper
|
|
11
14
|
|
|
12
15
|
def setup(**)
|
|
13
|
-
return
|
|
16
|
+
return unless enabled?
|
|
17
|
+
return if connected?
|
|
14
18
|
|
|
15
19
|
settings = local_settings
|
|
16
20
|
return unless settings[:enabled]
|
|
@@ -19,12 +23,12 @@ module Legion
|
|
|
19
23
|
@driver_name = Legion::Cache::Settings.normalize_driver(driver_name)
|
|
20
24
|
@driver = build_driver(driver_name)
|
|
21
25
|
@driver.client(**settings, logger: log, **)
|
|
22
|
-
@connected
|
|
26
|
+
@connected.make_true
|
|
23
27
|
servers = Array(settings[:servers]).join(', ')
|
|
24
28
|
log.info "Legion::Cache::Local connected (#{driver_name}) to #{servers}"
|
|
25
29
|
rescue StandardError => e
|
|
26
30
|
handle_exception(e, level: :warn, handled: true, operation: :cache_local_setup, driver: driver_name)
|
|
27
|
-
@connected
|
|
31
|
+
@connected.make_false
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def shutdown
|
|
@@ -34,11 +38,20 @@ module Legion
|
|
|
34
38
|
@driver&.close
|
|
35
39
|
@driver = nil
|
|
36
40
|
@driver_name = nil
|
|
37
|
-
@connected
|
|
41
|
+
@connected.make_false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def enabled?
|
|
45
|
+
return true unless defined?(Legion::Settings)
|
|
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
|
|
38
51
|
end
|
|
39
52
|
|
|
40
53
|
def connected?
|
|
41
|
-
@connected
|
|
54
|
+
@connected&.true? || false
|
|
42
55
|
end
|
|
43
56
|
|
|
44
57
|
def driver_name
|
|
@@ -47,47 +60,82 @@ module Legion
|
|
|
47
60
|
|
|
48
61
|
def get(key)
|
|
49
62
|
result = @driver.get(key)
|
|
50
|
-
log.debug "[cache:local] GET #{key} hit=#{!result.nil?}"
|
|
63
|
+
log.debug { "[cache:local] GET #{key} hit=#{!result.nil?}" }
|
|
51
64
|
result
|
|
52
65
|
rescue StandardError => e
|
|
53
|
-
handle_exception(e, level: :warn, handled:
|
|
54
|
-
|
|
66
|
+
handle_exception(e, level: :warn, handled: true, operation: :cache_local_get, key: key)
|
|
67
|
+
nil
|
|
55
68
|
end
|
|
56
69
|
|
|
57
|
-
def set(key, value, ttl
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
def set(key, value, ttl: nil, **)
|
|
71
|
+
set_sync(key, value, ttl: ttl, **)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def set_sync(key, value, ttl: nil, **)
|
|
75
|
+
effective_ttl = ttl || local_default_ttl
|
|
76
|
+
result = @driver.set_sync(key, value, ttl: effective_ttl)
|
|
77
|
+
log.debug { "[cache:local] SET #{key} ttl=#{effective_ttl} success=#{result}" }
|
|
60
78
|
result
|
|
61
79
|
rescue StandardError => e
|
|
62
|
-
handle_exception(e, level: :
|
|
80
|
+
handle_exception(e, level: :error, handled: false, operation: :cache_local_set_sync, key: key, ttl: effective_ttl)
|
|
63
81
|
raise
|
|
64
82
|
end
|
|
65
83
|
|
|
66
|
-
def fetch(key, ttl
|
|
67
|
-
result = @driver.fetch(key, ttl, &)
|
|
68
|
-
log.debug "[cache:local] FETCH #{key} hit=#{!result.nil?}"
|
|
84
|
+
def fetch(key, ttl: nil, &)
|
|
85
|
+
result = @driver.fetch(key, ttl: ttl, &)
|
|
86
|
+
log.debug { "[cache:local] FETCH #{key} hit=#{!result.nil?}" }
|
|
69
87
|
result
|
|
70
88
|
rescue StandardError => e
|
|
71
|
-
handle_exception(e, level: :warn, handled:
|
|
72
|
-
|
|
89
|
+
handle_exception(e, level: :warn, handled: true, operation: :cache_local_fetch, key: key, ttl: ttl)
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def delete(key, **)
|
|
94
|
+
delete_sync(key)
|
|
73
95
|
end
|
|
74
96
|
|
|
75
|
-
def
|
|
76
|
-
result = @driver.
|
|
77
|
-
log.debug "[cache:local] DELETE #{key} success=#{result}"
|
|
97
|
+
def delete_sync(key)
|
|
98
|
+
result = @driver.delete_sync(key)
|
|
99
|
+
log.debug { "[cache:local] DELETE #{key} success=#{result}" }
|
|
78
100
|
result
|
|
79
101
|
rescue StandardError => e
|
|
80
|
-
handle_exception(e, level: :
|
|
102
|
+
handle_exception(e, level: :error, handled: false, operation: :cache_local_delete_sync, key: key)
|
|
81
103
|
raise
|
|
82
104
|
end
|
|
83
105
|
|
|
84
|
-
def flush
|
|
85
|
-
result = @driver.flush
|
|
86
|
-
log.debug '[cache:local] FLUSH completed'
|
|
106
|
+
def flush
|
|
107
|
+
result = @driver.flush
|
|
108
|
+
log.debug { '[cache:local] FLUSH completed' }
|
|
87
109
|
result
|
|
88
110
|
rescue StandardError => e
|
|
89
|
-
handle_exception(e, level: :warn, handled:
|
|
90
|
-
|
|
111
|
+
handle_exception(e, level: :warn, handled: true, operation: :cache_local_flush)
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def mget(*keys)
|
|
116
|
+
keys = keys.flatten
|
|
117
|
+
return {} if keys.empty?
|
|
118
|
+
|
|
119
|
+
keys.to_h { |key| [key, get(key)] }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def mset(hash, ttl: nil, **)
|
|
123
|
+
return true if hash.empty?
|
|
124
|
+
|
|
125
|
+
hash.each { |key, value| set(key, value, ttl: ttl) }
|
|
126
|
+
true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
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
|
|
91
139
|
end
|
|
92
140
|
|
|
93
141
|
def client
|
|
@@ -98,7 +146,7 @@ module Legion
|
|
|
98
146
|
@driver&.close
|
|
99
147
|
@driver = nil
|
|
100
148
|
@driver_name = nil
|
|
101
|
-
@connected
|
|
149
|
+
@connected.make_false
|
|
102
150
|
log.info 'Legion::Cache::Local pool closed'
|
|
103
151
|
@connected
|
|
104
152
|
end
|
|
@@ -106,7 +154,7 @@ module Legion
|
|
|
106
154
|
def restart(**opts)
|
|
107
155
|
settings = local_settings
|
|
108
156
|
@driver&.restart(**settings.merge(opts, logger: log))
|
|
109
|
-
@connected
|
|
157
|
+
@connected.make_true
|
|
110
158
|
log.info 'Legion::Cache::Local pool restarted'
|
|
111
159
|
@connected
|
|
112
160
|
end
|
|
@@ -130,13 +178,27 @@ module Legion
|
|
|
130
178
|
def reset!
|
|
131
179
|
@driver = nil
|
|
132
180
|
@driver_name = nil
|
|
133
|
-
@connected
|
|
181
|
+
@connected.make_false
|
|
134
182
|
log.debug 'Legion::Cache::Local state reset'
|
|
135
183
|
@connected
|
|
136
184
|
end
|
|
137
185
|
|
|
138
186
|
private
|
|
139
187
|
|
|
188
|
+
def local_servers
|
|
189
|
+
Array(local_settings[:servers])
|
|
190
|
+
rescue StandardError
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def local_default_ttl
|
|
195
|
+
return 21_600 unless defined?(Legion::Settings)
|
|
196
|
+
|
|
197
|
+
Legion::Settings.dig(:cache_local, :default_ttl) || 21_600
|
|
198
|
+
rescue StandardError
|
|
199
|
+
21_600
|
|
200
|
+
end
|
|
201
|
+
|
|
140
202
|
def build_driver(driver_name)
|
|
141
203
|
case Legion::Cache::Settings.normalize_driver(driver_name)
|
|
142
204
|
when 'redis'
|