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 +4 -4
- data/README.md +1 -1
- data/lib/hot_cell/cell.rb +4 -4
- data/lib/hot_cell/client/version.rb +1 -1
- data/lib/hot_cell/client.rb +10 -4
- data/lib/hot_cell/failures.rb +11 -9
- data/lib/hot_cell/install/Dockerfile.tt +1 -1
- data/lib/hot_cell/log_subscriber.rb +1 -1
- data/lib/hot_cell/transport.rb +7 -4
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68844b319a6ccd7835ede7e0c6b364fa3bc3d7a29a211b921c8f6adc2198902a
|
|
4
|
+
data.tar.gz: d8e6dcbd8421be86289184abfca146020d1ae40434beceaffce48464b20d5e45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f93722bb025dde95a5399d987b94e41d38512ca47f51966a1f1032b104c5a3fda5d90727500c3c49202faa8e6b865105d7e269e2777b0ad8a4f310437fab477
|
|
7
|
+
data.tar.gz: d7a833dec4d14ae77456f998ba9915b06cb6f5502d2e11342e4a5e8d2ef0e498d675a723793ed223199b70810ffbd70ebf6aa82140800cd4b88e99613e38caa0
|
data/README.md
CHANGED
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
|
|
15
|
-
#
|
|
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
|
|
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
|
|
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
|
|
data/lib/hot_cell/client.rb
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
data/lib/hot_cell/failures.rb
CHANGED
|
@@ -22,16 +22,18 @@ module HotCell
|
|
|
22
22
|
# failure into a permanent one.
|
|
23
23
|
class TransientFailure < StandardError; end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
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]
|
|
31
|
+
code: payload[:code],
|
|
32
32
|
exception: payload[:exception]&.first,
|
|
33
33
|
cause: payload[:cause],
|
|
34
34
|
perform_ms: payload[:perform_ms],
|
data/lib/hot_cell/transport.rb
CHANGED
|
@@ -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
|
|
17
|
-
# fails with ENOTCONN,
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
75
|
-
changelog_uri: https://github.com/basecamp/hotcell/blob/v1.
|
|
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.
|
|
93
|
+
rubygems_version: 4.0.6
|
|
94
94
|
specification_version: 4
|
|
95
|
-
summary: Call a
|
|
95
|
+
summary: Call a Hot Cell from an application.
|
|
96
96
|
test_files: []
|