- AsyncWriter TOCTOU race condition in enqueue (capture local executor reference)
- Reconnector deadlock on stop (release mutex before thread.join)
- Reconnector NoMethodError on successful reconnect (AtomicFixnum reset)
- Missing
require 'concurrent'in reconnector.rb - Redis cluster flush now passes auth/TLS credentials to per-node connections
- Async writer drains before pool close on shutdown
- Serialization applied to mget/mset_sync (was only on set_sync/get)
- Binary encoding forced before serialization prefix checks
- Automatic failback to Local tier when shared cache is disabled or disconnected (configurable via
failback_to_local: true) mget/msetmethods on Memory adapter for interface consistency- Public
poolaccessor onLegion::Cache(replaces direct@clientaccess) failed_countcounter in AsyncWriter stats (async_failedin stats hash)reconnect_shared!raising method for reconnector connect_block- End-to-end lifecycle integration test (shared fail -> local failback -> reconnect)
- Helper and Cacheable use
async: falsefor read-after-write consistency - AsyncWriter and Reconnector are tier-aware (
settings_key:parameter,:cache_localfor local tier) - Redis driver
pool_sizeresolved from settings (was hardcoded to 20) - Pool checkout timeout separated from operation timeout (new
pool_checkout_timeoutsetting) - Reconnector starts on any shared failure (even when local fallback succeeds)
setupguarded byenabled?check- State flags (
@connected,@using_local,@using_memory) refactored toConcurrent::AtomicBoolean - Reconnector
@stop_signalrefactored toConcurrent::AtomicBoolean RedisHashuses publicpoolaccessor instead ofinstance_variable_get(:@client)
- Async write support via
Legion::Cache::AsyncWriterbacked byconcurrent-rubyThreadPoolExecutor set,delete, andmsetnow acceptasync:keyword (defaulttrue) for non-blocking writesLegion::Cache::Reconnectorwith exponential backoff (1s to 60s) for background reconnection- Reconnector auto-starts when both shared and local cache are unavailable at setup
enabled?guard on both shared and local tiersstatsmethod returning frozen Hash with driver, connection, pool, async, and reconnect metricsset_sync,delete_sync,mset_syncexplicit synchronous write methods on all tiers- Async and reconnect default settings in
Legion::Cache::Settings - Transparent JSON serialization for Redis driver (prefix-byte protocol, backward-compatible with legacy data)
- All cache drivers now use keyword TTL (
ttl:) instead of positional arguments flushtakes no arguments across all drivers (wasflush(delay = 0))Helpermodule updated:FALLBACK_TTLchanged from 60 to 3600, all delegations use keyword signatures,cache_set/cache_delete/cache_msetacceptasync:keywordCacheablemodule updated:cache_writeandlocal_cache_writeuse keyword TTL- Default TTL changed from 60 to 3600 (shared) and 21600 (local)
- Version bump from 1.3.22 to 1.4.0
- Unified exception handling model: reads return nil (handled), sync writes re-raise, lifecycle handles internally
- Default
serversno longer pre-computed at require time with stale driver port; resolves Redis connecting to127.0.0.1:11211(memcached default) instead of user-configured host
- Preserve
fetchblock behavior across shared, local, memory, and Redis cache paths; local-cache failures now fall back to the in-process cache and cachedfalsevalues are retained correctly - Move shared adapter selection to runtime setup/client calls, register cache defaults through
Legion::Cache::Settings, and normalize IPv4/hostname/IPv6 server addresses consistently - Restrict Redis hash/sorted-set helpers to the actual Redis backend and enforce documented TTL behavior for helper batch writes
- Make the default
bundle exec rspecsuite hermetic by excluding service-backed integration specs unlessRUN_INTEGRATION_SPECS=1
- Uplift cache logging internals to
Legion::Logging::Helper, replacing direct logger calls with helper-providedlogusage across cache runtime modules - Route rescued cache adapter/helper/setup failures through
handle_exceptionand expand runtimeinfo/debug/errorcoverage for shared, local, memory, pool, and RedisHash flows - Require
legion-logging >= 1.5.0at runtime so helper exception handling is always available
- Forward
timeoutsetting to::Redis.new— was silently using redis gem's 1.0s default instead of configured 5s, causing spurious timeouts on service mesh connections - Forward
timeoutto::Redis.newin cluster mode path as well
- Increase
reconnect_attemptsfrom1to[0, 0.5, 1](shared) /[0, 0.25, 0.5](local) — 3 retries with escalating backoff instead of 1 instant retry, improving resilience for service mesh and remote Redis connections
cache_mget/cache_mset(andlocal_cache_mget/local_cache_mset) onHelpermixin — delegates to Redis batch ops, falls back to sequential get/set on Memcached (closes #3)cache_hset,cache_hgetall,cache_hdel,cache_zadd,cache_zrangebyscore,cache_zrem,cache_expireonHelpermixin — delegates toRedisHashwith namespace prefixing; hash ops fall back to JSON-serialized Memcached values, sorted-set ops raiseNotImplementedError, expire is a no-op on Memcached (closes #4)
- Layered TTL resolution in Helper (per-call → LEX override → Settings → FALLBACK_TTL)
cache_default_ttl/local_cache_default_ttl— LEX-overridable default TTL methodscache_exist?/local_cache_exist?— key existence checkscache_connected?/local_cache_connected?— connection status helperscache_pool_size/cache_pool_available— pool info (shared tier)local_cache_pool_size/local_cache_pool_available— pool info (local tier)phi:keyword argument oncache_set/local_cache_setfor PHI TTL enforcementdefault_ttlkey in Settings.default and Settings.local (defaults to 60)
Legion::Cache::RedisHashmodule: Redis hash and sorted-set operations (hset,hgetall,hdel,zadd,zrangebyscore,zrem,expire) withredis_available?guard and safe defaults when Redis is not connected- Auto-required from
legion/cache.rbalongside the existing Redis adapter
- Accept ttl as positional or keyword argument in Cache.set for caller flexibility
- Align Redis.set signature to positional ttl arg matching parent module convention
- PHI-aware TTL enforcement:
Cache.setacceptsphi: truekeyword option; TTL is capped atcache.compliance.phi_max_ttl(default 3600s) when set Legion::Cache.phi_max_ttl— readscache.compliance.phi_max_ttlfrom settings with 3600s defaultLegion::Cache.enforce_phi_ttl(ttl, phi:)— public helper for PHI TTL cap logic
username,password,db, andreconnect_attemptsoptions to Redisclientandbuild_redis_client- Corresponding nil/default entries in
Settings.defaultandSettings.local
- Reindex docs: update CLAUDE.md and README with Memory adapter and Helper mixin docs
Legion::Cache::Memoryadapter module for lite mode: pure in-memory cache with TTL expiry and thread-safe Mutex synchronization- Cache
setupauto-detectsLEGION_MODE=liteand activates Memory adapter, skipping Redis/Memcached @using_memoryflag routesget/set/fetch/delete/flushthrough Memory adaptershutdowncleanly tears down Memory adapter when active
Legion::Cache::Helpermodule: injectable cache mixin for LEX extensions- Namespaced
cache_set,cache_get,cache_delete,cache_fetchfor shared cache - Namespaced
local_cache_set,local_cache_get,local_cache_delete,local_cache_fetchfor per-node local cache
- Updated gemspec dependency version constraints: legion-logging >= 1.2.8, legion-settings >= 1.3.12
- Added
Legion::Loggingcalls (guarded withdefined?) to all previously silent rescue blocks memcached.rb: debug log onmemcached_tls_settingsfailureredis.rb: warn log oncluster_flushfallback; debug log onresolved_redis_addressandcache_tls_settingsfailuressettings.rb: stdlibwarnonlegion/settingsrequire failure (Logging not yet available at that point)
- Memcached driver now logs server addresses on connect
- Local cache now logs server addresses on connect
- Shared cache setup log now shows full server list instead of just first server
- Redis driver:
.debuglogging on get (hit/miss), set (ttl/success), delete, flush, mget (key count), mset (key count) - Redis driver:
.infoon successful client creation with host/port address - Redis driver: private
resolved_redis_addresshelper for extracting address at connect time - Pool:
.infoon close and restart - Cacheable:
.debugon cache hit/miss in wrapper;.warnon swallowed errors inlocal_cache_read/local_cache_write - Local:
.debugon get/set/fetch/delete/flush operations - Cache:
.infoon successful shared cache setup (driver + server) - All new logging calls guarded with
if defined?(Legion::Logging)for standalone use
- Redis Cluster mode:
cluster:,replica:,fixed_hostname:options inbuild_redis_client cluster_mode?predicate for runtime cluster detectionmget(*keys)andmset(hash)with automatic slot-aware grouping for cross-slot operations- Cluster-aware
flushthat iterates all primary nodes viaCLUSTER NODES - Failover logging:
Redis::BaseErrorrescues log viaLegion::Logging.warnbefore re-raising - Settings defaults:
cluster: nil,replica: false,fixed_hostname: nil
get,set,delete,flushvisibility changed from private to public (were inaccessible on the module directly)
- TLS support for Redis driver:
ssl: true+ssl_paramswhen TLS enabled viaLegion::Crypt::TLS.resolve - TLS support for Memcached driver:
ssl_contextoption when TLS enabled viaLegion::Crypt::TLS.resolve - Port-based auto-detection: Redis TLS port 6380, Memcached TLS port 11207
- Serializer option (
Legion::JSON) now correctly flows through toDalli::Client.new, preventing Dalli from falling back to Marshal and emitting a security warning
Legion::Cache::Cacheablemodule for transparent method-level cachingcache_methodDSL: declare cached methods with TTL, scope, and key exclusionsbuild_cache_key: deterministic MD5-based cache keys from module path + method + filtered argsbypass_local_method_cache:kwarg for force-refresh on cached methods- In-memory fallback store with TTL expiry when no cache backend is available
memory_clear!class method for test isolation
Settings.normalize_driver— maps:memcached,:dalli,:redisto internal gem namesSettings.resolve_servers— mergesserver:(string) andservers:(array), injects default port per driver (memcached: 11211, redis: 6379), deduplicatesSettings::DEFAULT_PORTSconstant for driver default ports
- Redis driver now uses configured
server:/servers:instead of hardcoded localhost - Memcached driver accepts
server:(singular) in addition toservers:(plural)
Settings.defaultandSettings.localuseresolve_serversfor driver-aware server defaults- Driver selection in
cache.rbandlocal.rbusesnormalize_driverfor consistent name handling
Legion::Cache::Localmodule for local Redis/Memcached cachingSettings.localwith independent defaults (namespace:legion_local, pool_size: 5, timeout: 3)- Transparent fallback: shared cache failure at setup redirects all operations to Local
Legion::Cache.localaccessor,Legion::Cache.using_local?query
- Set dalli
value_max_bytesto 8MB by default — dalli enforces a 1MB client-side limit that prevented large cache values from being stored even when memcached server allows larger items
Moving from BitBucket to GitHub. All git history is reset from this point on