Class: CovLoupe::ModelDataCache
- Inherits:
-
Object
- Object
- CovLoupe::ModelDataCache
- Defined in:
- lib/cov_loupe/model/model_data_cache.rb
Overview
Thread-safe singleton cache for ModelData instances. Entries are keyed by [resultset_path, root] and automatically invalidated when the file changes.
Cache invalidation uses a two-layer check:
1. Signature: file mtime + size + inode (cheap, no file read)
2. Digest: MD5 hash of file contents (catches same-mtime edits on coarse-grained filesystems)
Both must match for a cache hit. If either differs, fresh data is loaded.
The cache key includes both resultset_path and root because path normalization and case-sensitivity detection depend on the root directory. Two models with the same resultset but different roots may have different normalized coverage maps.
Why a singleton? CoverageModel instances are lightweight (created per request in MCP mode), but loading and normalizing the resultset is expensive. The singleton cache ensures that repeated requests for the same resultset reuse the parsed data until the file changes.
Constant Summary collapse
- INSTANCE_MUTEX =
Mutex for thread-safe singleton initialization. Using a constant ensures it cannot be reset, avoiding race conditions in JRuby.
Mutex.new
Class Method Summary collapse
-
.instance ⇒ Object
Returns the singleton instance with thread-safe initialization.
Instance Method Summary collapse
-
#clear ⇒ Object
Clears all cached entries (primarily for testing).
-
#get(resultset_path, root:, logger: nil) ⇒ ModelData
Fetches ModelData for the given resultset path.
-
#initialize ⇒ ModelDataCache
constructor
A new instance of ModelDataCache.
Constructor Details
#initialize ⇒ ModelDataCache
Returns a new instance of ModelDataCache.
29 30 31 32 |
# File 'lib/cov_loupe/model/model_data_cache.rb', line 29 def initialize @entries = {} @mutex = Mutex.new end |
Class Method Details
.instance ⇒ Object
Returns the singleton instance with thread-safe initialization
35 36 37 38 39 |
# File 'lib/cov_loupe/model/model_data_cache.rb', line 35 def self.instance INSTANCE_MUTEX.synchronize do @instance ||= new end end |
Instance Method Details
#clear ⇒ Object
Clears all cached entries (primarily for testing)
92 93 94 |
# File 'lib/cov_loupe/model/model_data_cache.rb', line 92 def clear @mutex.synchronize { @entries.clear } end |
#get(resultset_path, root:, logger: nil) ⇒ ModelData
Complexity: O(1) amortized for cache hits. For cache misses, O(n) where n is the size of the resultset file, plus O(m) for parsing where m is total lines. File stat and MD5 digest are O(1) relative to file size on most filesystems.
Thread-safety: Thread-safe. This method uses a Mutex to synchronize access to the internal cache entries hash. Concurrent calls from multiple threads are guaranteed to return consistent results without data races.
Fetches ModelData for the given resultset path. Checks signature/digest on every call and reloads if the file has changed.
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 |
# File 'lib/cov_loupe/model/model_data_cache.rb', line 55 def get(resultset_path, root:, logger: nil) @mutex.synchronize do # Cache key must include both resultset_path and root because # path normalization and case-sensitivity depend on the root cache_key = [resultset_path, root] entry = @entries[cache_key] # Signature (mtime/size/inode) is cheap — no file read required. Digest (MD5) # is a fallback guard for filesystems with coarse mtime precision where two # different writes can land with the same timestamp. signature = compute_signature(resultset_path) digest = compute_digest(resultset_path) # Both must match: signature catches most changes; digest catches same-mtime edits. if entry && signature && digest && entry[:signature] == signature && entry[:digest] == digest return entry[:data] end # Load fresh data using the provided logger data = load_data(resultset_path, root, logger) # Store with signature/digest if we computed them if signature && digest @entries[cache_key] = { data: data, signature: signature, digest: digest, } end data end end |