Class: HotCell::Configuration
- Inherits:
-
Object
- Object
- HotCell::Configuration
- Defined in:
- lib/hot_cell/configuration.rb
Overview
A cell is configured once, and everything about scheduling lives here rather than on an operation.
These numbers are arithmetic against the container's own flags, not defaults to copy. Against a cell
given two CPUs, 2GB, and a 512MB tmpfs: concurrency 4 because a request spends much of its life off
the CPU, so twice cpus is where to start; file_size 64MB because it bounds what one worker writes
onto that tmpfs, and four of them share it; and memory 1536MB because that is the measured working
value for RLIMIT_DATA, which is per worker and mostly reservation rather than resident bytes.
What a worker writes is its outputs plus a copy of any input an operation asked for by path. An operation that consumes the descriptor never pays for its input, which is what lets a small file_size accept a large upload — but the kernel does not distinguish the two writes, so one number covers both.
memory does not multiply by concurrency, and that is the easiest mistake to make here. It is an address-space charge on one worker. The cgroup limit is what bounds real memory across the cell, and it counts the tmpfs too, so size the two separately.
Constant Summary collapse
- SCHEDULING =
{ concurrency: 4, # workers running at once, and therefore the number of slots queue_size: 8, # connections that may be accepted and waiting for one queue_wait: 10, # seconds a queued connection may wait before it is answered `capacity` max_requests_per_worker: 1, # requests a worker serves before it is discarded control_deadline: 5, # seconds a control connection may take to send its request }.freeze
- LIMITS =
{ deadline: 60, memory: 1536 * 1024**2, file_size: 64 * 1024**2, open_files: 256, }.freeze
- UNLIMITED =
:unlimited- KILL_GRACE =
Noticing an overdue worker, signalling it, reaping it, and writing the answer. Also the budget a retired worker gets to exit before the supervisor kills its group — see Supervisor#enforce_retirements.
1
Instance Attribute Summary collapse
-
#limits ⇒ Object
readonly
Returns the value of attribute limits.
Instance Method Summary collapse
-
#answer_within ⇒ Object
What this cell expects to answer within, and deliberately not a bound it can keep: a worker in uninterruptible sleep does not die when it is signalled, and the supervisor answers only after the reap.
-
#initialize(**options) ⇒ Configuration
constructor
A new instance of Configuration.
- #retire?(served) ⇒ Boolean
-
#to_h ⇒ Object
Goes on the wire as the answer to hotcell.describe, so every value has to be JSON-native.
- #unlimited_requests? ⇒ Boolean
Constructor Details
#initialize(**options) ⇒ Configuration
Returns a new instance of Configuration.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/hot_cell/configuration.rb', line 44 def initialize(**) unknown = .keys - SCHEDULING.keys - Limits::KEYS raise ConfigurationError, "unknown setting #{unknown.join(", ")}" if unknown.any? SCHEDULING.each { |key, default| instance_variable_set :"@#{key}", .fetch(key, default) } # The two second-valued settings, coerced for the same reason Limits coerces deadline: they appear # in describe's JSON, and they may arrive as Active Support durations. @queue_wait = @queue_wait.to_f @control_deadline = @control_deadline.to_f # A nil is not "use the default" here, it is a missing number. A cell whose deadline is nil accepts # every request and then dies on the first arithmetic the supervisor does with it, so an explicit nil # has to be refused where it is written rather than where it is used. declared = .slice(*Limits::KEYS) if (empty = declared.select { |_, value| value.nil? }.keys).any? raise ConfigurationError, "#{empty.join(", ")} cannot be nil; leave it out to take the default" end @limits = Limits.new(**LIMITS.merge(declared)) verify! end |
Instance Attribute Details
#limits ⇒ Object (readonly)
Returns the value of attribute limits.
42 43 44 |
# File 'lib/hot_cell/configuration.rb', line 42 def limits @limits end |
Instance Method Details
#answer_within ⇒ Object
What this cell expects to answer within, and deliberately not a bound it can keep: a worker in uninterruptible sleep does not die when it is signalled, and the supervisor answers only after the reap. Treat it as the threshold a client's timeout should clear, not as a guarantee to build a correctness argument on.
Derived here rather than reassembled in the client: a client adding up queue_wait + deadline plus its
own guess at the kill-to-answer step cannot follow a change to any of them, and the error points the
unsafe way — the client believes its timeout is generous and takes a transport failure instead of the
cell's verdict. A stage added later that costs a caller time belongs in this sum.
85 86 87 |
# File 'lib/hot_cell/configuration.rb', line 85 def answer_within queue_wait + limits.deadline + KILL_GRACE end |
#retire?(served) ⇒ Boolean
72 73 74 |
# File 'lib/hot_cell/configuration.rb', line 72 def retire?(served) !unlimited_requests? && served >= max_requests_per_worker end |
#to_h ⇒ Object
Goes on the wire as the answer to hotcell.describe, so every value has to be JSON-native.
max_requests_per_worker is the one that is not: :unlimited is a Symbol, and a cell configured with it could
not describe itself at all.
92 93 94 95 96 97 |
# File 'lib/hot_cell/configuration.rb', line 92 def to_h SCHEDULING.keys.to_h { |key| [ key, public_send(key) ] } .merge(limits.to_h) .merge(answer_within: answer_within, max_requests_per_worker: unlimited_requests? ? UNLIMITED.to_s : max_requests_per_worker) end |
#unlimited_requests? ⇒ Boolean
68 69 70 |
# File 'lib/hot_cell/configuration.rb', line 68 def unlimited_requests? max_requests_per_worker == UNLIMITED end |