hotcell-client 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ba1bb0047670fef637ea304c58ca6589c68c68491b84df357745f4c35dba740
4
- data.tar.gz: ec3e1cac68b95f6af2c00982538ce95a19a1257014c1c8bec53a49a860e68f83
3
+ metadata.gz: 68844b319a6ccd7835ede7e0c6b364fa3bc3d7a29a211b921c8f6adc2198902a
4
+ data.tar.gz: d8e6dcbd8421be86289184abfca146020d1ae40434beceaffce48464b20d5e45
5
5
  SHA512:
6
- metadata.gz: 26eaf78d5f835becb66508a423f91f73223e843d531e6d44723c5d635f9cab1b16f7560f58fc322ab25b50c6503d6d6227d88665a0ca2f81533de19e16f3b84d
7
- data.tar.gz: e12b678e13d8327c502ae440e923970dd7bcd32b1e828ba9d1ffedabbf724e678c6d36a63bf412d50b289be46c513698f62af187d4a35a6ee13a8d573bfe16a5
6
+ metadata.gz: 1f93722bb025dde95a5399d987b94e41d38512ca47f51966a1f1032b104c5a3fda5d90727500c3c49202faa8e6b865105d7e269e2777b0ad8a4f310437fab477
7
+ data.tar.gz: d7a833dec4d14ae77456f998ba9915b06cb6f5502d2e11342e4a5e8d2ef0e498d675a723793ed223199b70810ffbd70ebf6aa82140800cd4b88e99613e38caa0
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # hotcell-client
2
2
 
3
- Part of [HotCell](https://github.com/basecamp/hotcell). See the repository README.
3
+ Part of [Hot Cell](https://github.com/basecamp/hotcell). See the repository README.
data/lib/hot_cell/cell.rb CHANGED
@@ -11,8 +11,8 @@ module HotCell
11
11
  class Cell
12
12
  attr_reader :name, :timeout, :control_timeout, :permanent, :transient, :transport
13
13
 
14
- # `timeout` covers work, so it is sized to clear the cell's `answer_within` and a saturated cell reports
15
- # its own verdict rather than a transport failure. `control_timeout` covers `describe` and `metrics`,
14
+ # `timeout` covers work, so it is sized to clear the cell's `answer_within` and a saturated cell answers
15
+ # for itself rather than arriving as a transport failure. `control_timeout` covers `describe` and `metrics`,
16
16
  # which the supervisor answers inline with no fork or queue — so it is short on purpose. Sharing one
17
17
  # number would give the call whose job is to say "this cell is down" the patience of a video transcode.
18
18
  #
@@ -189,7 +189,7 @@ module HotCell
189
189
  # Being bound tighter than that is defensible and it is a choice, not a mistake. A synchronous
190
190
  # representation request wants it tighter, because a thread held for sixty seconds is a thread not
191
191
  # serving traffic. A background job wants it looser, so it receives `capacity` or `killed` and can act
192
- # on them rather than guessing from a socket error. Both outcomes are transient, so neither is
192
+ # on them rather than guessing from a socket error. Both failures are transient, so neither is
193
193
  # misclassified — which is the only reason this is safe.
194
194
  # The cell states this; adding it up here would mean guessing at stages only the supervisor knows about.
195
195
  # A cell too old to report it says nothing, which is the right answer for a number we cannot know.
@@ -199,7 +199,7 @@ module HotCell
199
199
 
200
200
  HotCell.logger.warn "hotcell #{name}: this client waits #{seconds timeout} and the cell says it may " \
201
201
  "take #{seconds needed} to answer, so a saturated cell will arrive here as a " \
202
- "transport failure rather than as its own verdict. Deliberate on a synchronous " \
202
+ "transport failure rather than as the cell's own answer. Deliberate on a synchronous " \
203
203
  "path; a mistake for a background job."
204
204
  end
205
205
 
@@ -3,6 +3,6 @@
3
3
  module HotCell
4
4
  # A class and not a module: this file loads before hot_cell/client.rb opens the same name.
5
5
  class Client
6
- VERSION = "1.1.0"
6
+ VERSION = "1.2.0"
7
7
  end
8
8
  end
@@ -106,6 +106,11 @@ module HotCell
106
106
  ActiveSupport::Notifications.instrument "perform.hot_cell", operation: self.class.operation, cell: cell.name do |event|
107
107
  response = verify_output(cell.transport.call(cell, line, descriptors), outputs)
108
108
  publish event, cell, response, inputs, outputs
109
+ # The transport answers its own failures with a code, so what lands here is the caller's own exception,
110
+ # such as its request timeout, and the call has no answer to describe.
111
+ rescue Exception
112
+ event[:code] = "interrupted"
113
+ raise
109
114
  end
110
115
 
111
116
  raise_for response, cell
@@ -160,9 +165,10 @@ module HotCell
160
165
  # `code` belongs on the event rather than only on an exception. A caller configured to treat
161
166
  # `unreadable` as data would otherwise make those requests invisible, and unreadable rates are exactly
162
167
  # what you want to watch after a library upgrade. `capacity` matters for the same reason: without its
163
- # rate you cannot size a worker pool.
168
+ # rate you cannot size a worker pool. It is `ok` on success, so no subscriber reads success from a
169
+ # missing code.
164
170
  #
165
- # The cause, signal and verdict go with it, because the code alone cannot classify a kill: `killed`
171
+ # The cause, signal and `permanent` go with it, because the code alone cannot classify a kill: `killed`
166
172
  # is permanent for fsize and memory and transient for deadline and crashed. A subscriber with only
167
173
  # the code filed every fsize kill as transient. `permanent` is the Failure's own answer, so no
168
174
  # subscriber re-derives it.
@@ -178,7 +184,7 @@ module HotCell
178
184
  def publish(event, cell, response, inputs, outputs)
179
185
  failure = response.failure
180
186
 
181
- event[:code] = failure&.code
187
+ event[:code] = failure ? failure.code : "ok"
182
188
  event[:cause] = failure&.cause
183
189
  event[:signal] = failure&.signal
184
190
  event[:stderr] = failure&.stderr
@@ -200,7 +206,7 @@ module HotCell
200
206
  return if response.ok?
201
207
 
202
208
  failure = response.failure
203
- error = Verdict.wrap(cell.exception_for(failure).new(failure.to_s), failure)
209
+ error = Failure::Exception.wrap(cell.exception_for(failure).new(failure.to_s), failure)
204
210
  cell.report_contract_skew error if failure.code == "protocol"
205
211
 
206
212
  raise error
@@ -22,16 +22,18 @@ module HotCell
22
22
  # failure into a permanent one.
23
23
  class TransientFailure < StandardError; end
24
24
 
25
- # Extended onto every exception the client raises, so an application can read the failure's code,
26
- # cause and permanence without parsing the message — and rescue HotCell::Verdict itself, whatever
27
- # classes the registration injected.
28
- module Verdict
29
- attr_reader :hot_cell_failure
25
+ class Failure
26
+ # Extended onto every exception the client raises, so an application can read the failure's code,
27
+ # cause and permanence without parsing the message — and rescue HotCell::Failure::Exception itself,
28
+ # whatever classes the registration injected.
29
+ module Exception
30
+ attr_reader :hot_cell_failure
30
31
 
31
- def self.wrap(error, failure)
32
- error.extend(self)
33
- error.instance_variable_set(:@hot_cell_failure, failure)
34
- error
32
+ def self.wrap(error, failure)
33
+ error.extend(self)
34
+ error.instance_variable_set(:@hot_cell_failure, failure)
35
+ error
36
+ end
35
37
  end
36
38
  end
37
39
  end
@@ -1,4 +1,4 @@
1
- # The cell's image. HotCell installed this file once; it is yours to customize, and nothing inherits
1
+ # The cell's image. Hot Cell installed this file once; it is yours to customize, and nothing inherits
2
2
  # from a published base image — this is the whole recipe.
3
3
  #
4
4
  # A cell carries a Ruby runtime and every gem in its loaded graph, all inside the blast radius, so treat
@@ -28,7 +28,7 @@ module HotCell
28
28
  " HotCell (#{duration_ms}ms) " + JSON.generate({
29
29
  cell: payload[:cell],
30
30
  operation: payload[:operation],
31
- code: payload[:code] || ("ok" unless payload[:exception]),
31
+ code: payload[:code],
32
32
  exception: payload[:exception]&.first,
33
33
  cause: payload[:cause],
34
34
  perform_ms: payload[:perform_ms],
@@ -13,9 +13,9 @@ module HotCell
13
13
  # `timeout` covers the answer and not the connection. On Linux a blocking `connect` to a Unix socket waits
14
14
  # in the kernel while the listener's backlog is full, which is where a supervisor that stops calling
15
15
  # `accept` leaves it. `UNIXSocket.new` does not wait: Ruby opens every socket non-blocking and takes the
16
- # kernel's EAGAIN for a connection in progress, so it returns a socket that never connected, the send
17
- # fails with ENOTCONN, and the caller gets `unavailable` at once. That is Ruby's behavior rather than
18
- # this gem's, so ControlTimeoutTest holds it.
16
+ # kernel's EAGAIN for a connection in progress, so it returns a socket that never connected. The send
17
+ # fails with ENOTCONN, which `deliver` ignores, then the read fails with EINVAL, and the caller gets
18
+ # `unavailable` at once. That is Ruby's behavior rather than this gem's, so ControlTimeoutTest holds it.
19
19
  #
20
20
  # If Ruby ever starts waiting, `connect_nonblock` plus `wait_writable` against a deadline would bound the
21
21
  # call only by spinning: on Linux an unconnected Unix socket is always writable, so it retries EAGAIN until
@@ -39,9 +39,12 @@ module HotCell
39
39
  # then fail while the answer is already waiting on the socket. So this ignores the failed send, and
40
40
  # `receive` reads the answer. If the peer closed without an answer, `receive` reports that the
41
41
  # supervisor is gone.
42
+ #
43
+ # Linux fails that send with EPIPE or ECONNRESET. macOS usually does too, but answers ENOTCONN when the
44
+ # close lands while its kernel is copying the request in, between checking the socket and queueing.
42
45
  def deliver(connection, line, descriptors)
43
46
  connection.send_message line, descriptors: descriptors
44
- rescue Errno::EPIPE, Errno::ECONNRESET
47
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN
45
48
  nil
46
49
  end
47
50
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotcell-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Dalessio
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 1.1.0
18
+ version: 1.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 1.1.0
25
+ version: 1.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activesupport
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +38,7 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: '7.1'
40
40
  description: |
41
- Call operations that run in a HotCell container. Register a cell, subclass HotCell::Client to name
41
+ Call operations that run in a Hot Cell container. Register a cell, subclass HotCell::Client to name
42
42
  an operation, and call it with open file descriptors and a payload. Failures arrive classified as
43
43
  permanent or transient, as exception classes your application supplies.
44
44
  email:
@@ -71,8 +71,8 @@ licenses:
71
71
  - MIT
72
72
  metadata:
73
73
  homepage_uri: https://github.com/basecamp/hotcell
74
- source_code_uri: https://github.com/basecamp/hotcell/tree/v1.1.0/hotcell-client
75
- changelog_uri: https://github.com/basecamp/hotcell/blob/v1.1.0/CHANGELOG.md
74
+ source_code_uri: https://github.com/basecamp/hotcell/tree/v1.2.0/hotcell-client
75
+ changelog_uri: https://github.com/basecamp/hotcell/blob/v1.2.0/CHANGELOG.md
76
76
  documentation_uri: https://basecamp.github.io/hotcell/
77
77
  bug_tracker_uri: https://github.com/basecamp/hotcell/issues
78
78
  rubygems_mfa_required: 'true'
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
90
  - !ruby/object:Gem::Version
91
91
  version: '0'
92
92
  requirements: []
93
- rubygems_version: 4.0.16
93
+ rubygems_version: 4.0.6
94
94
  specification_version: 4
95
- summary: Call a HotCell from an application.
95
+ summary: Call a Hot Cell from an application.
96
96
  test_files: []