Class: HotCell::Supervisor::Child
- Inherits:
-
Struct
- Object
- Struct
- HotCell::Supervisor::Child
- Includes:
- Timed
- Defined in:
- lib/hot_cell/supervisor.rb
Overview
Owns "is this worker busy" and the two transitions that change the answer, because the supervisor asking
busy? and the supervisor assigning the four fields busy? is computed from are the same fact. Spread
across the caller, a new field is one the next transition forgets to clear.
Instance Attribute Summary collapse
-
#buffer ⇒ Object
Returns the value of attribute buffer.
-
#captured ⇒ Object
Returns the value of attribute captured.
-
#connection ⇒ Object
Returns the value of attribute connection.
-
#control ⇒ Object
Returns the value of attribute control.
-
#deadline ⇒ Object
Returns the value of attribute deadline.
-
#dispatched_at ⇒ Object
Returns the value of attribute dispatched_at.
-
#killed_for ⇒ Object
Returns the value of attribute killed_for.
-
#op ⇒ Object
Returns the value of attribute op.
-
#pid ⇒ Object
Returns the value of attribute pid.
-
#retired_at ⇒ Object
Returns the value of attribute retired_at.
-
#served ⇒ Object
Returns the value of attribute served.
-
#slot ⇒ Object
Returns the value of attribute slot.
-
#stderr ⇒ Object
Returns the value of attribute stderr.
Class Method Summary collapse
Instance Method Summary collapse
- #available? ⇒ Boolean
- #busy? ⇒ Boolean
-
#capture_stderr(chunk) ⇒ Object
Keeps the last MAX_MESSAGE_BYTES rather than the first, because the line that ended the request is the last one written.
- #dispatched(connection, deadline, at:) ⇒ Object
-
#finished ⇒ Object
Not a reset of
deadline: the supervisor re-establishes it on the next dispatch, and leaving the last one readable is what lets a log line after the fact say what this worker was being held to. -
#lingering?(now, grace) ⇒ Boolean
The retirement analogue of
overdue?, and the only timer that can reach a worker whose idle report was early:finishclears whatoverdue?reads, so a worker still running what it reported finished answers to this and nothing else. - #lingers_until(grace) ⇒ Object
-
#released ⇒ Object
Stop being busy while leaving the connection open, for the one caller that still has to answer on it.
- #started_at ⇒ Object
-
#stderr_field ⇒ Object
A silent worker gets no field at all:
Log#documentdoes not compact the hotcell namespace, so a nil would put"stderr":nullon every death a cell reports. -
#timed? ⇒ Boolean
A child already killed for its deadline stops being a timer, and that guard is load-bearing rather than tidy.
Methods included from Timed
Instance Attribute Details
#buffer ⇒ Object
Returns the value of attribute buffer
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def buffer @buffer end |
#captured ⇒ Object
Returns the value of attribute captured
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def captured @captured end |
#connection ⇒ Object
Returns the value of attribute connection
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def connection @connection end |
#control ⇒ Object
Returns the value of attribute control
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def control @control end |
#deadline ⇒ Object
Returns the value of attribute deadline
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def deadline @deadline end |
#dispatched_at ⇒ Object
Returns the value of attribute dispatched_at
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def dispatched_at @dispatched_at end |
#killed_for ⇒ Object
Returns the value of attribute killed_for
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def killed_for @killed_for end |
#op ⇒ Object
Returns the value of attribute op
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def op @op end |
#pid ⇒ Object
Returns the value of attribute pid
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def pid @pid end |
#retired_at ⇒ Object
Returns the value of attribute retired_at
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def retired_at @retired_at end |
#served ⇒ Object
Returns the value of attribute served
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def served @served end |
#slot ⇒ Object
Returns the value of attribute slot
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def slot @slot end |
#stderr ⇒ Object
Returns the value of attribute stderr
42 43 44 |
# File 'lib/hot_cell/supervisor.rb', line 42 def stderr @stderr end |
Class Method Details
.build(slot:, pid:, control:, deadline:, stderr: nil) ⇒ Object
46 47 48 49 |
# File 'lib/hot_cell/supervisor.rb', line 46 def self.build(slot:, pid:, control:, deadline:, stderr: nil) new slot: slot, pid: pid, control: control, deadline: deadline, served: 0, buffer: "".b, stderr: stderr, captured: "".b end |
Instance Method Details
#available? ⇒ Boolean
95 96 97 |
# File 'lib/hot_cell/supervisor.rb', line 95 def available? !busy? && retired_at.nil? end |
#busy? ⇒ Boolean
91 92 93 |
# File 'lib/hot_cell/supervisor.rb', line 91 def busy? !connection.nil? end |
#capture_stderr(chunk) ⇒ Object
Keeps the last MAX_MESSAGE_BYTES rather than the first, because the line that ended the request is the last one written. Trimmed on every read, so a worker that never stops printing costs no more than that.
54 55 56 57 58 |
# File 'lib/hot_cell/supervisor.rb', line 54 def capture_stderr(chunk) captured << chunk excess = captured.bytesize - Failure::MAX_MESSAGE_BYTES captured.slice! 0, excess if excess.positive? end |
#dispatched(connection, deadline, at:) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/hot_cell/supervisor.rb', line 66 def dispatched(connection, deadline, at:) # An earlier request's warning is not this request's death. Not a boundary, though: a descendant # holding fd 2 writes whenever it likes, and a sibling can open /proc/<pid>/fd/2 and write there too. captured.clear self.connection = connection self.dispatched_at = at self.deadline = deadline self.killed_for = nil self.op = nil self.served += 1 end |
#finished ⇒ Object
Not a reset of deadline: the supervisor re-establishes it on the next dispatch, and leaving the last
one readable is what lets a log line after the fact say what this worker was being held to.
80 81 82 83 |
# File 'lib/hot_cell/supervisor.rb', line 80 def finished connection&.close released end |
#lingering?(now, grace) ⇒ Boolean
The retirement analogue of overdue?, and the only timer that can reach a worker whose idle
report was early: finish clears what overdue? reads, so a worker still running what it
reported finished answers to this and nothing else. A retired worker has nothing left to do but
exit — its closed control socket ends its await_dispatch — so the wait for it is bounded where an
available worker's is not. Busy is excluded because a busy retired child is a worker that already
died mid-request, which the reap answers for. killed_for is the same one-kill latch overdue?
reads.
121 122 123 |
# File 'lib/hot_cell/supervisor.rb', line 121 def lingering?(now, grace) !busy? && killed_for.nil? && !retired_at.nil? && now - retired_at >= grace end |
#lingers_until(grace) ⇒ Object
125 126 127 |
# File 'lib/hot_cell/supervisor.rb', line 125 def lingers_until(grace) retired_at + grace if !busy? && killed_for.nil? && !retired_at.nil? end |
#released ⇒ Object
Stop being busy while leaving the connection open, for the one caller that still has to answer on it.
86 87 88 89 |
# File 'lib/hot_cell/supervisor.rb', line 86 def released self.connection = nil self.dispatched_at = nil end |
#started_at ⇒ Object
110 111 112 |
# File 'lib/hot_cell/supervisor.rb', line 110 def started_at dispatched_at end |
#stderr_field ⇒ Object
A silent worker gets no field at all: Log#document does not compact the hotcell namespace, so a nil
would put "stderr":null on every death a cell reports.
62 63 64 |
# File 'lib/hot_cell/supervisor.rb', line 62 def stderr_field captured.empty? ? {} : { stderr: Failure.sanitize(captured, keep: :tail) } end |
#timed? ⇒ Boolean
A child already killed for its deadline stops being a timer, and that guard is load-bearing rather
than tidy. Killing does not clear busy? — the supervisor still holds the connection it has to answer
on — so without it expires_at stays in the past, wait_for returns 0, IO.select returns at once,
and the loop re-kills and re-logs on every pass until the reap. Measured at a 0.2s deadline: 72
SIGKILLs and 72 synchronous stdout writes for one breach. The window is longest exactly when the host
is already struggling — a worker in uninterruptible sleep, or one tearing down gigabytes of mappings —
and the loop it starves is the one enforcing every other request's deadline.
106 107 108 |
# File 'lib/hot_cell/supervisor.rb', line 106 def timed? busy? && killed_for.nil? end |