async-container 0.20.0 → 0.21.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: a95321eeaead4fafe8c8db6bfa186d13559c8b39185c566db474ff2111e3add8
4
- data.tar.gz: 873a71e53157cf3f5c94908daff4848f88b0bb06c35e10fd89c569fe6a646386
3
+ metadata.gz: 6dd27e02912c5f1258419c9821da1bd97d58f4fd68b4a58512b3ebd5291c5b6b
4
+ data.tar.gz: 36fc9d2d0ce94a0e2591beac3384d2fba288094a93c1ada7a9000841c1e78a59
5
5
  SHA512:
6
- metadata.gz: e974514dc85a284427791fffa2299c1541f4330b60f4d1470a64c9d2591d9c9c5a59250d9560e4b61ca43024869681f5f2cdc89ab086b178a391105318691b9d
7
- data.tar.gz: 712370e1ea844ede123bb6215422c393109e50133db7991242b748f013e75904494af9300fdcbcd5de490c65c473fb195eac46eabbb5d5f34e1a774235cf5e8e
6
+ metadata.gz: 162f3604bbaa973b486fc271ccf61106008a5d2259f114134535188349267ee3dfb81cbe857ce7edc49dd2037b5d1f5581dd531955ddbfba264a113638e300cd
7
+ data.tar.gz: b1ba5f8e3b96976a97b6f81e20a62efbcbde89e4e2f0be43c754954a44924e2553c447cf275121ce74894d0044e7677a144c7f43df5f77d7a55b263f6442eb96
checksums.yaml.gz.sig CHANGED
Binary file
@@ -180,6 +180,13 @@ module Async
180
180
  end
181
181
  end
182
182
 
183
+ # Send `SIGKILL` to the child process.
184
+ def kill!
185
+ unless @status
186
+ ::Process.kill(:KILL, @pid)
187
+ end
188
+ end
189
+
183
190
  # Send `SIGHUP` to the child process.
184
191
  def restart!
185
192
  unless @status
@@ -39,7 +39,7 @@ module Async
39
39
  UNNAMED = "Unnamed"
40
40
 
41
41
  def initialize(**options)
42
- @group = Group.new
42
+ @group = Group.new(**options)
43
43
  @running = true
44
44
 
45
45
  @state = {}
@@ -48,8 +48,10 @@ module Async
48
48
  @keyed = {}
49
49
  end
50
50
 
51
+ # @attribute [Group] The group of running children instances.
51
52
  attr :group
52
53
 
54
+ # @attribute [Hash(Child, Hash)] The state of each child instance.
53
55
  attr :state
54
56
 
55
57
  # A human readable representation of the container.
@@ -170,8 +172,8 @@ module Async
170
172
  when :health_check!
171
173
  if health_check_timeout&.<(age_clock.total)
172
174
  Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout)
173
- # If the child has failed the health check, we assume the worst and terminate it (SIGTERM).
174
- child.terminate!
175
+ # If the child has failed the health check, we assume the worst and kill it immediately:
176
+ child.kill!
175
177
  end
176
178
  else
177
179
  state.update(message)
@@ -15,7 +15,8 @@ module Async
15
15
  # @parameter count [Integer] The number of instances to start.
16
16
  # @parameter forks [Integer] The number of processes to fork.
17
17
  # @parameter threads [Integer] the number of threads to start.
18
- def run(count: nil, forks: nil, threads: nil, **options, &block)
18
+ # @parameter health_check_timeout [Numeric] The timeout for health checks, in seconds. Passed into the child {Threaded} containers.
19
+ def run(count: nil, forks: nil, threads: nil, health_check_timeout: nil, **options, &block)
19
20
  processor_count = Container.processor_count
20
21
  count ||= processor_count ** 2
21
22
  forks ||= [processor_count, count].min
@@ -25,7 +26,7 @@ module Async
25
26
  self.spawn(**options) do |instance|
26
27
  container = Threaded.new
27
28
 
28
- container.run(count: threads, **options, &block)
29
+ container.run(count: threads, health_check_timeout: health_check_timeout, **options, &block)
29
30
 
30
31
  container.wait_until_ready
31
32
  instance.ready!
@@ -34,6 +35,7 @@ module Async
34
35
  rescue Async::Container::Terminate
35
36
  # Stop it immediately:
36
37
  container.stop(false)
38
+ raise
37
39
  ensure
38
40
  # Stop it gracefully (also code path for Interrupt):
39
41
  container.stop
@@ -11,6 +11,9 @@ module Async
11
11
  module Container
12
12
  # A multi-thread container which uses {Thread.fork}.
13
13
  class Threaded < Generic
14
+ class Kill < Exception
15
+ end
16
+
14
17
  # Indicates that this is not a multi-process container.
15
18
  def self.multiprocess?
16
19
  false
@@ -178,6 +181,14 @@ module Async
178
181
  @thread.raise(Terminate)
179
182
  end
180
183
 
184
+ # Invoke {Thread#kill} on the child thread.
185
+ def kill!
186
+ # Killing a thread does not raise an exception in the thread, so we need to handle the status here:
187
+ @status = Status.new(:killed)
188
+
189
+ @thread.kill
190
+ end
191
+
181
192
  # Raise {Restart} in the child thread.
182
193
  def restart!
183
194
  @thread.raise(Restart)
@@ -230,7 +241,7 @@ module Async
230
241
  Console.error(self) {error}
231
242
  end
232
243
 
233
- @status = Status.new(error)
244
+ @status ||= Status.new(error)
234
245
  self.close_write
235
246
  end
236
247
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Container
8
- VERSION = "0.20.0"
8
+ VERSION = "0.21.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -14,7 +14,28 @@ Provides containers which implement parallelism for clients and servers.
14
14
 
15
15
  ## Usage
16
16
 
17
- Please see the [project documentation](https://socketry.github.io/async-container/).
17
+ Please see the [project documentation](https://socketry.github.io/async-container/) for more details.
18
+
19
+ - [Getting Started](https://socketry.github.io/async-container/guides/getting-started/index) - This guide explains how to use `async-container` to build basic scalable systems.
20
+
21
+ ## Releases
22
+
23
+ Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases.
24
+
25
+ ### v0.21.0
26
+
27
+ - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state.
28
+
29
+ ### v0.20.1
30
+
31
+ - Fix compatibility between <code class="language-ruby">Async::Container::Hybrid</code> and the health check.
32
+ - <code class="language-ruby">Async::Container::Generic\#initialize</code> passes unused arguments through to <code class="language-ruby">Async::Container::Group</code>.
33
+
34
+ ### v0.20.0
35
+
36
+ - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points.
37
+ - Improved logging when child process fails and container startup.
38
+ - [Add `health_check_timeout` for detecting hung processes.](https://socketry.github.io/async-container/releases/index#add-health_check_timeout-for-detecting-hung-processes.)
18
39
 
19
40
  ## Contributing
20
41
 
data/releases.md CHANGED
@@ -1,6 +1,51 @@
1
1
  # Releases
2
2
 
3
- ## Unreleased
3
+ ## v0.21.0
4
+
5
+ - Use `SIGKILL`/`Thread#kill` when the health check fails. In some cases, `SIGTERM` may not be sufficient to terminate a process because the signal can be ignored or the process may be in an uninterruptible state.
6
+
7
+ ## v0.20.1
8
+
9
+ - Fix compatibility between {ruby Async::Container::Hybrid} and the health check.
10
+ - {ruby Async::Container::Generic\#initialize} passes unused arguments through to {ruby Async::Container::Group}.
11
+
12
+ ## v0.20.0
4
13
 
5
14
  - Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points.
6
15
  - Improved logging when child process fails and container startup.
16
+
17
+ ### Add `health_check_timeout` for detecting hung processes.
18
+
19
+ In order to detect hung processes, a `health_check_timeout` can be specified when spawning children workers. If the health check does not complete within the specified timeout, the child process is killed.
20
+
21
+ ``` ruby
22
+ require "async/container"
23
+
24
+ container = Async::Container.new
25
+
26
+ container.run(count: 1, restart: true, health_check_timeout: 1) do |instance|
27
+ while true
28
+ # This example will fail sometimes:
29
+ sleep(0.5 + rand)
30
+ instance.ready!
31
+ end
32
+ end
33
+
34
+ container.wait
35
+ ```
36
+
37
+ If the health check does not complete within the specified timeout, the child process is killed:
38
+
39
+ ```
40
+ 3.01s warn: Async::Container::Forked [oid=0x1340] [ec=0x1348] [pid=27100] [2025-02-20 13:24:55 +1300]
41
+ | Child failed health check!
42
+ | {
43
+ | "child": {
44
+ | "name": "Unnamed",
45
+ | "pid": 27101,
46
+ | "status": null
47
+ | },
48
+ | "age": 1.0612829999881797,
49
+ | "health_check_timeout": 1
50
+ | }
51
+ ```
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-container
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -40,7 +40,7 @@ cert_chain:
40
40
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
41
41
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
42
42
  -----END CERTIFICATE-----
43
- date: 2025-02-07 00:00:00.000000000 Z
43
+ date: 2025-02-20 00:00:00.000000000 Z
44
44
  dependencies:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: async
metadata.gz.sig CHANGED
Binary file