Class: Restless::RecoveryCache

Inherits:
Object
  • Object
show all
Defined in:
lib/restless/caches.rb

Overview

CACHE-010..015. Agent Recovery messages keyed by error fingerprint.

Performance is the whole point. The lookup sits on the hot path of every 4xx/5xx, so it is synchronous, in-process and never does I/O. A cold miss injects nothing and returns immediately; the server piggybacks the message onto the next upload response, so the SECOND occurrence hits.

"No message for this fingerprint" is itself a cacheable answer, stored as nil, so a cold miss does not stay cold on every request. The negative TTL is shorter so a freshly-attached dashboard message starts working within minutes.

Constant Summary collapse

DEFAULT_TTL_MS =

CACHE-014: 1 hour, positive

3_600_000
DEFAULT_NEGATIVE_TTL_MS =

CACHE-014: 5 minutes, negative

300_000
MISS =

Distinguishes "cached as absent" (nil) from "never seen" (MISS).

Object.new.freeze

Instance Method Summary collapse

Constructor Details

#initialize(ttl_ms = DEFAULT_TTL_MS, negative_ttl_ms = DEFAULT_NEGATIVE_TTL_MS) ⇒ RecoveryCache

Returns a new instance of RecoveryCache.



83
84
85
86
87
88
# File 'lib/restless/caches.rb', line 83

def initialize(ttl_ms = DEFAULT_TTL_MS, negative_ttl_ms = DEFAULT_NEGATIVE_TTL_MS)
  @ttl_ms = ttl_ms
  @negative_ttl_ms = negative_ttl_ms
  @entries = {}
  @mutex = Mutex.new
end

Instance Method Details

#clearObject



133
134
135
# File 'lib/restless/caches.rb', line 133

def clear
  @mutex.synchronize { @entries.clear }
end

#get(key) ⇒ Object

Returns a String (inject it), nil (the server confirmed there is none), or MISS (never seen, or expired).



92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/restless/caches.rb', line 92

def get(key)
  @mutex.synchronize do
    entry = @entries[key]
    next MISS if entry.nil?

    ttl = entry[:message].nil? ? @negative_ttl_ms : @ttl_ms
    if now_ms - entry[:ts] > ttl # CACHE-015
      @entries.delete(key)
      next MISS
    end
    entry[:message]
  end
end

#invalidate(key) ⇒ Object



129
130
131
# File 'lib/restless/caches.rb', line 129

def invalidate(key)
  @mutex.synchronize { @entries.delete(key) }
end

#lookup(key) ⇒ Object

Convenience for the hot path: the message to inject, or nil.



107
108
109
110
# File 'lib/restless/caches.rb', line 107

def lookup(key)
  value = get(key)
  value.is_a?(String) ? value : nil
end

#set(key, message) ⇒ Object



112
113
114
# File 'lib/restless/caches.rb', line 112

def set(key, message)
  @mutex.synchronize { @entries[key] = { message: message, ts: now_ms } }
end

#set_negative_unless_present(key) ⇒ Object

CACHE-013. A negative entry must never overwrite a positive one.



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/restless/caches.rb', line 117

def set_negative_unless_present(key)
  @mutex.synchronize do
    entry = @entries[key]
    if entry
      ttl = entry[:message].nil? ? @negative_ttl_ms : @ttl_ms
      fresh = now_ms - entry[:ts] <= ttl
      next if fresh && !entry[:message].nil?
    end
    @entries[key] = { message: nil, ts: now_ms }
  end
end

#sizeObject



137
138
139
# File 'lib/restless/caches.rb', line 137

def size
  @mutex.synchronize { @entries.size }
end