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.

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

Classes: Child, Pending

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of Supervisor.



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/hot_cell/supervisor.rb', line 110

def initialize(directory:, workspace: nil, configuration: HotCell.configuration, log: Log.new,
               ptrace_scope_path: PTRACE_SCOPE)
  @directory = directory
  @workspace = workspace || File.join(Dir.tmpdir, "hotcell-workspace")
  @configuration = configuration
  @log = log
  @ptrace_scope_path = ptrace_scope_path
  @children = {}
  @queue = []
  @control_pending = []
  @counters = Counters.new
  @stopping = false
end

Instance Attribute Details

#configurationObject (readonly)

Returns the value of attribute configuration.



108
109
110
# File 'lib/hot_cell/supervisor.rb', line 108

def configuration
  @configuration
end

#countersObject (readonly)

Returns the value of attribute counters.



108
109
110
# File 'lib/hot_cell/supervisor.rb', line 108

def counters
  @counters
end

#directoryObject (readonly)

Returns the value of attribute directory.



108
109
110
# File 'lib/hot_cell/supervisor.rb', line 108

def directory
  @directory
end

#logObject (readonly)

Returns the value of attribute log.



108
109
110
# File 'lib/hot_cell/supervisor.rb', line 108

def log
  @log
end

#workspaceObject (readonly)

Returns the value of attribute workspace.



108
109
110
# File 'lib/hot_cell/supervisor.rb', line 108

def workspace
  @workspace
end

Instance Method Details

#bootObject



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/hot_cell/supervisor.rb', line 124

def boot
  verify_socket_paths!
  verify_limits!
  verify_ptrace_scope!
  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, operations: Registry.names,
                         configuration: configuration.to_h
  self
end

#runObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/hot_cell/supervisor.rb', line 140

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

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