Class: HotCell::Supervisor::Child

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

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#bufferObject

Returns the value of attribute buffer

Returns:

  • (Object)

    the current value of buffer



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def buffer
  @buffer
end

#connectionObject

Returns the value of attribute connection

Returns:

  • (Object)

    the current value of connection



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def connection
  @connection
end

#controlObject

Returns the value of attribute control

Returns:

  • (Object)

    the current value of control



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def control
  @control
end

#deadlineObject

Returns the value of attribute deadline

Returns:

  • (Object)

    the current value of deadline



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def deadline
  @deadline
end

#dispatched_atObject

Returns the value of attribute dispatched_at

Returns:

  • (Object)

    the current value of dispatched_at



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def dispatched_at
  @dispatched_at
end

#killed_forObject

Returns the value of attribute killed_for

Returns:

  • (Object)

    the current value of killed_for



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def killed_for
  @killed_for
end

#pidObject

Returns the value of attribute pid

Returns:

  • (Object)

    the current value of pid



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def pid
  @pid
end

#retired_atObject

Returns the value of attribute retired_at

Returns:

  • (Object)

    the current value of retired_at



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def retired_at
  @retired_at
end

#servedObject

Returns the value of attribute served

Returns:

  • (Object)

    the current value of served



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def served
  @served
end

#slotObject

Returns the value of attribute slot

Returns:

  • (Object)

    the current value of slot



23
24
25
# File 'lib/hot_cell/supervisor.rb', line 23

def slot
  @slot
end

Class Method Details

.build(slot:, pid:, control:, deadline:) ⇒ Object



25
26
27
# File 'lib/hot_cell/supervisor.rb', line 25

def self.build(slot:, pid:, control:, deadline:)
  new slot: slot, pid: pid, control: control, deadline: deadline, served: 0, buffer: "".b
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'lib/hot_cell/supervisor.rb', line 54

def available?
  !busy? && retired_at.nil?
end

#busy?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/hot_cell/supervisor.rb', line 50

def busy?
  !connection.nil?
end

#dispatched(connection, deadline, at:) ⇒ Object



29
30
31
32
33
34
35
# File 'lib/hot_cell/supervisor.rb', line 29

def dispatched(connection, deadline, at:)
  self.connection = connection
  self.dispatched_at = at
  self.deadline = deadline
  self.killed_for = nil
  self.served += 1
end

#expires_atObject



69
70
71
# File 'lib/hot_cell/supervisor.rb', line 69

def expires_at
  dispatched_at + deadline if busy? && killed_for.nil?
end

#finishedObject

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.



39
40
41
42
# File 'lib/hot_cell/supervisor.rb', line 39

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.

Returns:

  • (Boolean)


80
81
82
# File 'lib/hot_cell/supervisor.rb', line 80

def lingering?(now, grace)
  !busy? && killed_for.nil? && !retired_at.nil? && now - retired_at >= grace
end

#lingers_until(grace) ⇒ Object



84
85
86
# File 'lib/hot_cell/supervisor.rb', line 84

def lingers_until(grace)
  retired_at + grace if !busy? && killed_for.nil? && !retired_at.nil?
end

#overdue?(now) ⇒ 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.

Returns:

  • (Boolean)


65
66
67
# File 'lib/hot_cell/supervisor.rb', line 65

def overdue?(now)
  busy? && killed_for.nil? && now - dispatched_at >= deadline
end

#releasedObject

Stop being busy while leaving the connection open, for the one caller that still has to answer on it.



45
46
47
48
# File 'lib/hot_cell/supervisor.rb', line 45

def released
  self.connection = nil
  self.dispatched_at = nil
end