Class: Usps::Support::HealthMonitor
- Inherits:
-
Object
- Object
- Usps::Support::HealthMonitor
- Defined in:
- lib/usps/support/health_monitor.rb
Overview
Keeps a process-local snapshot of the latest health-check results so /health can answer from memory instead of running the checks on the request thread. See doc/health_check_refresh_design.md for the rationale.
runner is a callable that runs one full round of checks and returns whatever the caller needs to render (HealthController returns { app:, results:, measurements: }). It is expected to bound its own checks; the monitor adds no timeout of its own.
One monitor exists per concrete HealthController subclass, per process. It is created lazily on the first request, so the background thread starts after Passenger forks its workers (threads do not survive fork).
Defined Under Namespace
Classes: Snapshot
Instance Method Summary collapse
-
#age_of(snapshot) ⇒ Object
Seconds since the snapshot was taken, on the monitor's own (monotonic) clock -- callers can't compare
snapshot.atagainst a wall clock of their own. -
#cached ⇒ Object
The last completed snapshot, or nil if none has been taken yet -- without refreshing, even when stale.
-
#current ⇒ Object
The latest results.
- #fresh?(snapshot) ⇒ Boolean
-
#initialize(interval:, max_age:, runner:, background: true, clock: nil) ⇒ HealthMonitor
constructor
interval: seconds between background refreshes max_age: a snapshot older than this is refreshed inline before use, so a wedged or never-started background thread can't serve dangerously old data runner: callable that runs one round of checks and returns the render data background: start the background refresher (false in tests, so refreshes are synchronous) clock: monotonic clock (seconds) to read; injectable for deterministic staleness tests.
-
#refresh ⇒ Object
Runs one round of checks and stores the snapshot; returns the fresh data.
-
#stop ⇒ Object
Stops the background refresher.
Constructor Details
#initialize(interval:, max_age:, runner:, background: true, clock: nil) ⇒ HealthMonitor
interval: seconds between background refreshes max_age: a snapshot older than this is refreshed inline before use, so a wedged or never-started background thread can't serve dangerously old data runner: callable that runs one round of checks and returns the render data background: start the background refresher (false in tests, so refreshes are synchronous) clock: monotonic clock (seconds) to read; injectable for deterministic staleness tests
25 26 27 28 29 30 31 32 |
# File 'lib/usps/support/health_monitor.rb', line 25 def initialize(interval:, max_age:, runner:, background: true, clock: nil) @interval = interval @max_age = max_age @runner = runner @background = background @clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) } @mutex = Mutex.new end |
Instance Method Details
#age_of(snapshot) ⇒ Object
Seconds since the snapshot was taken, on the monitor's own (monotonic) clock -- callers can't
compare snapshot.at against a wall clock of their own.
76 |
# File 'lib/usps/support/health_monitor.rb', line 76 def age_of(snapshot) = @clock.call - snapshot.at |
#cached ⇒ Object
The last completed snapshot, or nil if none has been taken yet -- without refreshing, even when stale. #current is for /health, which must answer accurately and may pay for an inline round; this is for callers on a user request thread (Usps::Support::DependencyGate), where running a check would reintroduce exactly the stall they exist to avoid. Starts the background refresher on first use so a process that never serves /health still gets one. Callers judge staleness themselves via #age_of.
68 69 70 71 72 |
# File 'lib/usps/support/health_monitor.rb', line 68 def cached ensure_running if @background @snapshot end |
#current ⇒ Object
The latest results. Serves a fresh cached snapshot without touching any dependency; on a cold (first call) or stale snapshot, refreshes inline so the answer is never dangerously old. Also starts the background refresher on first use. Returns the runner's data, or nil only if the very first refresh failed and there is no prior snapshot to fall back on.
38 39 40 41 42 43 44 45 |
# File 'lib/usps/support/health_monitor.rb', line 38 def current ensure_running if @background snapshot = @snapshot return snapshot.data if snapshot && fresh?(snapshot) refresh end |
#fresh?(snapshot) ⇒ Boolean
60 |
# File 'lib/usps/support/health_monitor.rb', line 60 def fresh?(snapshot) = (@clock.call - snapshot.at) <= @max_age |
#refresh ⇒ Object
Runs one round of checks and stores the snapshot; returns the fresh data. On a runner error, keeps the last good snapshot and returns its data (nil if there was none), so a transient failure to run the checks never blanks out /health.
50 51 52 53 54 55 56 57 58 |
# File 'lib/usps/support/health_monitor.rb', line 50 def refresh @mutex.synchronize do data = @runner.call @snapshot = Snapshot.new(data:, at: @clock.call) data end rescue StandardError @snapshot&.data end |
#stop ⇒ Object
Stops the background refresher. Used by tests; also safe to call on shutdown.
79 80 81 82 83 84 |
# File 'lib/usps/support/health_monitor.rb', line 79 def stop @mutex.synchronize do @thread&.kill @thread = nil end end |