Class: Wurk::Swarm::RestartCounters

Inherits:
Object
  • Object
show all
Defined in:
lib/wurk/swarm/restart_counters.rb

Overview

The supervisor's child-restart counts and current crash streak, for the /metrics of whichever child owns the health port. The parent runs no listener and has no Redis key of its own, so it writes them into an unlinked file it opened before the first fork: every child, respawns included, inherits the descriptor and reads the parent's latest write with one pread. No Redis key, no socket.

One writer (the supervise thread) and a fixed 40-byte record at offset 0. A pread racing the pwrite can mix fields from the two records; each field is still a value it held (counters only grow), so a scrape never exports one that never existed, and the next scrape reads the settled record.

Defined Under Namespace

Classes: Snapshot

Constant Summary collapse

REASONS =
%i[crash recycle liveness rolling].freeze
FORMAT =
'Q<5'
SIZE =
8 * (REASONS.size + 1)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file) ⇒ RestartCounters

Returns a new instance of RestartCounters.



37
38
39
40
41
42
# File 'lib/wurk/swarm/restart_counters.rb', line 37

def initialize(file)
  @io = file
  @restarts = REASONS.to_h { |reason| [reason, 0] }
  @crash_streak = 0
  write
end

Class Method Details

.open(dir = ::Dir.tmpdir, logger: nil) ⇒ Object

nil when no temp file can be opened; the swarm runs without the metric.



27
28
29
30
31
32
33
34
35
# File 'lib/wurk/swarm/restart_counters.rb', line 27

def self.open(dir = ::Dir.tmpdir, logger: nil)
  path = ::File.join(dir, "wurk-swarm-#{::Process.pid}-#{::SecureRandom.hex(6)}")
  file = ::File.new(path, ::File::RDWR | ::File::CREAT | ::File::EXCL, 0o600)
  ::File.unlink(path)
  new(file)
rescue ::SystemCallError => e
  logger&.warn { "swarm: restart counters unavailable (#{e.class}: #{e.message}); not exported" }
  nil
end

Instance Method Details

#close ⇒ Object



66
67
68
# File 'lib/wurk/swarm/restart_counters.rb', line 66

def close
  @io.close unless @io.closed?
end

#crash_streak=(streak) ⇒ Object



49
50
51
52
53
54
# File 'lib/wurk/swarm/restart_counters.rb', line 49

def crash_streak=(streak)
  return if streak == @crash_streak

  @crash_streak = streak
  write
end

#read ⇒ Object

The parent's latest record; nil when it cannot be read.



57
58
59
60
61
62
63
64
# File 'lib/wurk/swarm/restart_counters.rb', line 57

def read
  values = @io.pread(SIZE, 0)&.unpack(FORMAT)
  return nil unless values && values.size == SIZE / 8

  Snapshot.new(REASONS.zip(values).to_h, values.last)
rescue ::IOError, ::SystemCallError
  nil
end

#record_restart(reason) ⇒ Object



44
45
46
47
# File 'lib/wurk/swarm/restart_counters.rb', line 44

def record_restart(reason)
  @restarts[REASONS.include?(reason) ? reason : :crash] += 1
  write
end