Class: HotCell::Supervisor

Inherits:
Object
  • Object
show all
Defined in:
lib/hot_cell/supervisor.rb

Overview

Accepts, queues, dispatches, times, kills, reaps, and cleans up. It never evaluates image data.

That last rule is mechanical rather than defensive: libvips starts its thread pool on the first evaluation and that pool does not survive fork, so a supervisor that has touched an image forks workers that deadlock forever. Reading a request line would be harmless — it is a control message from the trusted side on a bounded buffer — but the supervisor does not need to, and staying out of the request is what lets it dispatch a connection whose descriptors are still queued on it.

peeked_op is the exception, on the one path where no worker will read the request.

Dispatching rather than letting workers accept is what makes the rest work. The supervisor needs to own the accept anyway, for the queue, for queued_ms, and to answer capacity. It also means the supervisor knows when every worker started its current request, which is what the deadline needs.

Defined Under Namespace

Modules: Timed Classes: Child, Pending, Sweep

Constant Summary collapse

SUN_PATH_MAX =

A path longer than this fails to bind with an error that does not say so. Darwin allows four fewer bytes than Linux, and control.sock is the longer of the two names, so it overflows first.

RUBY_PLATFORM.include?("darwin") ? 104 : 108
SOCKETS =
[ "work.sock", "control.sock" ].freeze
PTRACE_SCOPE =

Request memory is protected by kernel.yama.ptrace_scope >= 1, and nothing else protects it. That is a host sysctl no container flag can supply.

"/proc/sys/kernel/yama/ptrace_scope"
CONTROL_BACKLOG =

Only to bound the list. The channel's whole value is answering when nothing else does, so this is set far above any real scrape rate rather than as a throttle.

64
STDERR_READ_BYTES =

What one pass reads off a worker's fd 2, and how many reads the reap's final drain gets. Sized to clear a full pipe rather than to bound memory — Child#capture_stderr does that — so reading further would only mean a fresher tail.

16 * 1024
STDERR_FINAL_READS =
8

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(directory:, workspace: nil, development: false, configuration: HotCell.configuration, log: Log.new, ptrace_scope_path: PTRACE_SCOPE) ⇒ Supervisor

Returns a new instance of Supervisor.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/hot_cell/supervisor.rb', line 169

def initialize(directory:, workspace: nil, development: false, configuration: HotCell.configuration,
               log: Log.new, ptrace_scope_path: PTRACE_SCOPE)
  @directory = directory
  @development_tmpdir = own_tmpdir if development
  @workspace = workspace || File.join(tmpdir, "hotcell-workspace")
  @configuration = configuration
  @log = log
  @ptrace_scope_path = ptrace_scope_path
  @children = {}
  @queue = []
  @control_pending = []
  @counters = Counters.new
  @stopping = false
  @sweep = nil
  @next_sweep_at = Clock.now + configuration.sweep_interval
end

Instance Attribute Details

#configurationObject (readonly)

Returns the value of attribute configuration.



167
168
169
# File 'lib/hot_cell/supervisor.rb', line 167

def configuration
  @configuration
end

#countersObject (readonly)

Returns the value of attribute counters.



167
168
169
# File 'lib/hot_cell/supervisor.rb', line 167

def counters
  @counters
end

#directoryObject (readonly)

Returns the value of attribute directory.



167
168
169
# File 'lib/hot_cell/supervisor.rb', line 167

def directory
  @directory
end

#logObject (readonly)

Returns the value of attribute log.



167
168
169
# File 'lib/hot_cell/supervisor.rb', line 167

def log
  @log
end

#workspaceObject (readonly)

Returns the value of attribute workspace.



167
168
169
# File 'lib/hot_cell/supervisor.rb', line 167

def workspace
  @workspace
end

Instance Method Details

#bootObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/hot_cell/supervisor.rb', line 186

def boot
  verify_socket_paths!
  verify_limits!
  verify_ptrace_scope!
  claim_tmpdir if @development_tmpdir
  verify_scratches!
  prepare_directories
  preload
  @work = listen "work.sock"
  @control = listen "control.sock"
  @control_handler = Control.new(configuration: configuration, counters: counters)
  trap_signals

  log.write "cell.boot", pid: Process.pid, directory: directory, tmpdir: tmpdir, operations: Registry.names,
                         configuration: configuration.to_h
  self
end

#runObject



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/hot_cell/supervisor.rb', line 204

def run
  until stopped?
    readable, = IO.select(sources, nil, nil, wait_for)
    Array(readable).each { |source| handle source }

    enforce_deadlines
    enforce_retirements
    enforce_sweep_deadline
    expire_queue
    expire_control
    retire_idle if @stopping
    pump
    sweep_if_due
  end
ensure
  shutdown
end