async-container 0.19.0 → 0.20.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: 2edeb6fa679f328736bb4a65c3b342b4aea94f2fc79557d4fc6e9e84bee77a2e
4
- data.tar.gz: 5c1c52a4f90c8710efebb411d97e5a8342ea99d1bbe09082deed562b8acdce99
3
+ metadata.gz: a95321eeaead4fafe8c8db6bfa186d13559c8b39185c566db474ff2111e3add8
4
+ data.tar.gz: 873a71e53157cf3f5c94908daff4848f88b0bb06c35e10fd89c569fe6a646386
5
5
  SHA512:
6
- metadata.gz: ee365ff16248e3064b136cdfeddec8e0f01ef77decbd514b94fc4a3bce1a38c2c51ffa5ba1e071b2fcd6bad6b7c4d4e322f26e8e5a530e4f45a68266d67429da
7
- data.tar.gz: 171a93c5855cad3a112622232bbc7e93b880d4c77223cb8a17c7772a0687a0be40af3143a0ecad2b14a9e9aa656010cdf47eb42f80e14b08148cd02f4a4eaa6c
6
+ metadata.gz: e974514dc85a284427791fffa2299c1541f4330b60f4d1470a64c9d2591d9c9c5a59250d9560e4b61ca43024869681f5f2cdc89ab086b178a391105318691b9d
7
+ data.tar.gz: 712370e1ea844ede123bb6215422c393109e50133db7991242b748f013e75904494af9300fdcbcd5de490c65c473fb195eac46eabbb5d5f34e1a774235cf5e8e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -116,6 +116,24 @@ module Async
116
116
  self.close_write
117
117
  end
118
118
 
119
+ # Convert the child process to a hash, suitable for serialization.
120
+ #
121
+ # @returns [Hash] The request as a hash.
122
+ def as_json(...)
123
+ {
124
+ name: @name,
125
+ pid: @pid,
126
+ status: @status&.to_i,
127
+ }
128
+ end
129
+
130
+ # Convert the request to JSON.
131
+ #
132
+ # @returns [String] The request as JSON.
133
+ def to_json(...)
134
+ as_json.to_json(...)
135
+ end
136
+
119
137
  # Set the name of the process.
120
138
  # Invokes {::Process.setproctitle} if invoked in the child process.
121
139
  def name= value
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require "etc"
7
+ require "async/clock"
7
8
 
8
9
  require_relative "group"
9
10
  require_relative "keyed"
@@ -141,7 +142,8 @@ module Async
141
142
  # @parameter name [String] The name of the child instance.
142
143
  # @parameter restart [Boolean] Whether to restart the child instance if it fails.
143
144
  # @parameter key [Symbol] A key used for reloading child instances.
144
- def spawn(name: nil, restart: false, key: nil, &block)
145
+ # @parameter health_check_timeout [Numeric | Nil] The maximum time a child instance can run without updating its state, before it is terminated as unhealthy.
146
+ def spawn(name: nil, restart: false, key: nil, health_check_timeout: nil, &block)
145
147
  name ||= UNNAMED
146
148
 
147
149
  if mark?(key)
@@ -157,9 +159,24 @@ module Async
157
159
 
158
160
  state = insert(key, child)
159
161
 
162
+ # If a health check is specified, we will monitor the child process and terminate it if it does not update its state within the specified time.
163
+ if health_check_timeout
164
+ age_clock = state[:age] = Clock.start
165
+ end
166
+
160
167
  begin
161
168
  status = @group.wait_for(child) do |message|
162
- state.update(message)
169
+ case message
170
+ when :health_check!
171
+ if health_check_timeout&.<(age_clock.total)
172
+ 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
+ end
176
+ else
177
+ state.update(message)
178
+ age_clock&.reset!
179
+ end
163
180
  end
164
181
  ensure
165
182
  delete(key, child)
@@ -13,7 +13,12 @@ module Async
13
13
  # Manages a group of running processes.
14
14
  class Group
15
15
  # Initialize an empty group.
16
- def initialize
16
+ #
17
+ # @parameter health_check_interval [Numeric | Nil] The (biggest) interval at which health checks are performed.
18
+ def initialize(health_check_interval: 1.0)
19
+ @health_check_interval = health_check_interval
20
+
21
+ # The running fibers, indexed by IO:
17
22
  @running = {}
18
23
 
19
24
  # This queue allows us to wait for processes to complete, without spawning new processes as a result.
@@ -57,8 +62,36 @@ module Async
57
62
  def wait
58
63
  self.resume
59
64
 
60
- while self.running?
61
- self.wait_for_children
65
+ with_health_checks do |duration|
66
+ self.wait_for_children(duration)
67
+ end
68
+ end
69
+
70
+ private def with_health_checks
71
+ if @health_check_interval
72
+ health_check_clock = Clock.start
73
+
74
+ while self.running?
75
+ duration = [@health_check_interval - health_check_clock.total, 0].max
76
+
77
+ yield duration
78
+
79
+ if health_check_clock.total > @health_check_interval
80
+ self.health_check!
81
+ health_check_clock.reset!
82
+ end
83
+ end
84
+ else
85
+ while self.running?
86
+ yield nil
87
+ end
88
+ end
89
+ end
90
+
91
+ # Perform a health check on all running processes.
92
+ def health_check!
93
+ @running.each_value do |fiber|
94
+ fiber.resume(:health_check!)
62
95
  end
63
96
  end
64
97
 
@@ -119,15 +152,19 @@ module Async
119
152
  @running[io] = Fiber.current
120
153
 
121
154
  while @running.key?(io)
155
+ # Wait for some event on the channel:
122
156
  result = Fiber.yield
123
157
 
124
158
  if result == Interrupt
125
159
  channel.interrupt!
126
160
  elsif result == Terminate
127
161
  channel.terminate!
162
+ elsif result
163
+ yield result
128
164
  elsif message = channel.receive
129
165
  yield message
130
166
  else
167
+ # Wait for the channel to exit:
131
168
  return channel.wait
132
169
  end
133
170
  end
@@ -90,7 +90,10 @@ module Async
90
90
  def self.fork(**options)
91
91
  self.new(**options) do |thread|
92
92
  ::Thread.new do
93
- yield Instance.for(thread)
93
+ # This could be a configuration option (see forked implementation too):
94
+ ::Thread.handle_interrupt(SignalException => :immediate) do
95
+ yield Instance.for(thread)
96
+ end
94
97
  end
95
98
  end
96
99
  end
@@ -122,6 +125,23 @@ module Async
122
125
  end
123
126
  end
124
127
 
128
+ # Convert the child process to a hash, suitable for serialization.
129
+ #
130
+ # @returns [Hash] The request as a hash.
131
+ def as_json(...)
132
+ {
133
+ name: @thread.name,
134
+ status: @status&.as_json,
135
+ }
136
+ end
137
+
138
+ # Convert the request to JSON.
139
+ #
140
+ # @returns [String] The request as JSON.
141
+ def to_json(...)
142
+ as_json.to_json(...)
143
+ end
144
+
125
145
  # Set the name of the thread.
126
146
  # @parameter value [String] The name to set.
127
147
  def name= value
@@ -188,6 +208,14 @@ module Async
188
208
  @error.nil?
189
209
  end
190
210
 
211
+ def as_json(...)
212
+ if @error
213
+ @error.inspect
214
+ else
215
+ true
216
+ end
217
+ end
218
+
191
219
  # A human readable representation of the status.
192
220
  def to_s
193
221
  "\#<#{self.class} #{success? ? "success" : "failure"}>"
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Container
8
- VERSION = "0.19.0"
8
+ VERSION = "0.20.0"
9
9
  end
10
10
  end
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.19.0
4
+ version: 0.20.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-04 00:00:00.000000000 Z
43
+ date: 2025-02-07 00:00:00.000000000 Z
44
44
  dependencies:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: async
@@ -48,14 +48,14 @@ dependencies:
48
48
  requirements:
49
49
  - - "~>"
50
50
  - !ruby/object:Gem::Version
51
- version: '2.10'
51
+ version: '2.22'
52
52
  type: :runtime
53
53
  prerelease: false
54
54
  version_requirements: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - "~>"
57
57
  - !ruby/object:Gem::Version
58
- version: '2.10'
58
+ version: '2.22'
59
59
  executables: []
60
60
  extensions: []
61
61
  extra_rdoc_files: []
metadata.gz.sig CHANGED
Binary file