Class: Usps::Support::HealthMonitor

Inherits:
Object
  • Object
show all
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

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

#currentObject

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

Returns:

  • (Boolean)


60
# File 'lib/usps/support/health_monitor.rb', line 60

def fresh?(snapshot) = (@clock.call - snapshot.at) <= @max_age

#refreshObject

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

#stopObject

Stops the background refresher. Used by tests; also safe to call on shutdown.



63
64
65
66
67
68
# File 'lib/usps/support/health_monitor.rb', line 63

def stop
  @mutex.synchronize do
    @thread&.kill
    @thread = nil
  end
end