async-container 0.27.1 → 0.27.7
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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/container/channel.rb +7 -7
- data/lib/async/container/controller.rb +10 -11
- data/lib/async/container/forked.rb +2 -3
- data/lib/async/container/generic.rb +20 -6
- data/lib/async/container/group.rb +12 -7
- data/lib/async/container/notify/pipe.rb +2 -2
- data/lib/async/container/statistics.rb +18 -0
- data/lib/async/container/threaded.rb +2 -2
- data/lib/async/container/version.rb +1 -1
- data/license.md +1 -0
- data/readme.md +17 -15
- data/releases.md +17 -0
- data.tar.gz.sig +0 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e0f77eb0f6eb3e7e8f8971af4d653e5ff2c3243ecc12f8dd06b27ae27e09d45
|
|
4
|
+
data.tar.gz: 869aaa117d285f6f407b482b5fcd5b3bb19edcba215202d2faefe159195eed38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1eb59fda7b292a5ca70b15ea0b70af9d6f41551d9a4818f5820dbc9b078f3008d37f646e8cbcdc74428d3c359c9ec9e731b3b0b008a74922f379b700eda32e4
|
|
7
|
+
data.tar.gz: 301401ff8bcd736d2e41d1816110625ce038ff65b0a675d11e0ec900feb5550c73e6e754af9e5123dd12b692e4980deac1d197cbdad38e3a48213b38dd938171
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
7
|
|
|
@@ -10,8 +10,9 @@ module Async
|
|
|
10
10
|
# Provides a basic multi-thread/multi-process uni-directional communication channel.
|
|
11
11
|
class Channel
|
|
12
12
|
# Initialize the channel using a pipe.
|
|
13
|
-
def initialize
|
|
13
|
+
def initialize(timeout: 1.0)
|
|
14
14
|
@in, @out = ::IO.pipe
|
|
15
|
+
@in.timeout = timeout
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
# The input end of the pipe.
|
|
@@ -43,12 +44,11 @@ module Async
|
|
|
43
44
|
# @returns [Hash]
|
|
44
45
|
def receive
|
|
45
46
|
if data = @in.gets
|
|
46
|
-
|
|
47
|
-
return JSON.parse(data, symbolize_names: true)
|
|
48
|
-
rescue
|
|
49
|
-
return {line: data}
|
|
50
|
-
end
|
|
47
|
+
return JSON.parse(data, symbolize_names: true)
|
|
51
48
|
end
|
|
49
|
+
rescue => error
|
|
50
|
+
Console.error(self, "Error during channel receive!", error)
|
|
51
|
+
return nil
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -91,11 +91,11 @@ module Async
|
|
|
91
91
|
# Start the container unless it's already running.
|
|
92
92
|
def start
|
|
93
93
|
unless @container
|
|
94
|
-
Console.info(self
|
|
94
|
+
Console.info(self, "Controller starting...")
|
|
95
95
|
self.restart
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
Console.info(self
|
|
98
|
+
Console.info(self, "Controller started...")
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# Stop the container if it's running.
|
|
@@ -111,9 +111,9 @@ module Async
|
|
|
111
111
|
if @container
|
|
112
112
|
@notify&.restarting!
|
|
113
113
|
|
|
114
|
-
Console.
|
|
114
|
+
Console.info(self, "Restarting container...")
|
|
115
115
|
else
|
|
116
|
-
Console.
|
|
116
|
+
Console.info(self, "Starting container...")
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
container = self.create_container
|
|
@@ -127,13 +127,14 @@ module Async
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
# Wait for all child processes to enter the ready state.
|
|
130
|
-
Console.
|
|
130
|
+
Console.info(self, "Waiting for startup...")
|
|
131
131
|
container.wait_until_ready
|
|
132
|
-
Console.
|
|
132
|
+
Console.info(self, "Finished startup.")
|
|
133
133
|
|
|
134
134
|
if container.failed?
|
|
135
135
|
@notify&.error!("Container failed to start!")
|
|
136
136
|
|
|
137
|
+
Console.info(self, "Stopping failed container...")
|
|
137
138
|
container.stop(false)
|
|
138
139
|
|
|
139
140
|
raise SetupError, container
|
|
@@ -145,7 +146,7 @@ module Async
|
|
|
145
146
|
container = nil
|
|
146
147
|
|
|
147
148
|
if old_container
|
|
148
|
-
Console.
|
|
149
|
+
Console.info(self, "Stopping old container...")
|
|
149
150
|
old_container&.stop(@graceful_stop)
|
|
150
151
|
end
|
|
151
152
|
|
|
@@ -168,11 +169,9 @@ module Async
|
|
|
168
169
|
end
|
|
169
170
|
|
|
170
171
|
# Wait for all child processes to enter the ready state.
|
|
171
|
-
Console.
|
|
172
|
-
|
|
172
|
+
Console.info(self, "Waiting for startup...")
|
|
173
173
|
@container.wait_until_ready
|
|
174
|
-
|
|
175
|
-
Console.debug(self, "Finished startup.")
|
|
174
|
+
Console.info(self, "Finished startup.")
|
|
176
175
|
|
|
177
176
|
if @container.failed?
|
|
178
177
|
@notify.error!("Container failed to reload!")
|
|
@@ -136,8 +136,8 @@ module Async
|
|
|
136
136
|
|
|
137
137
|
# Initialize the process.
|
|
138
138
|
# @parameter name [String] The name to use for the child process.
|
|
139
|
-
def initialize(name: nil)
|
|
140
|
-
super()
|
|
139
|
+
def initialize(name: nil, **options)
|
|
140
|
+
super(**options)
|
|
141
141
|
|
|
142
142
|
@name = name
|
|
143
143
|
@status = nil
|
|
@@ -260,7 +260,6 @@ module Async
|
|
|
260
260
|
end
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
-
|
|
264
263
|
# Start a named child process and execute the provided block in it.
|
|
265
264
|
# @parameter name [String] The name (title) of the child process.
|
|
266
265
|
# @parameter block [Proc] The block to execute in the child process.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2019-2025, by Samuel Williams.
|
|
5
|
+
# Copyright, 2025, by Marc-André Cournoyer.
|
|
5
6
|
|
|
6
7
|
require "etc"
|
|
7
8
|
require "async/clock"
|
|
@@ -139,12 +140,18 @@ module Async
|
|
|
139
140
|
# Stop the children instances.
|
|
140
141
|
# @parameter timeout [Boolean | Numeric] Whether to stop gracefully, or a specific timeout.
|
|
141
142
|
def stop(timeout = true)
|
|
143
|
+
Console.info(self, "Stopping container...", timeout: timeout, caller: caller_locations)
|
|
142
144
|
@running = false
|
|
143
145
|
@group.stop(timeout)
|
|
144
146
|
|
|
145
147
|
if @group.running?
|
|
146
|
-
Console.warn(self
|
|
148
|
+
Console.warn(self, "Group is still running after stopping it!")
|
|
149
|
+
else
|
|
150
|
+
Console.info(self, "Group has stopped.")
|
|
147
151
|
end
|
|
152
|
+
rescue => error
|
|
153
|
+
Console.error(self, "Error while stopping container!", exception: error)
|
|
154
|
+
raise
|
|
148
155
|
ensure
|
|
149
156
|
@running = true
|
|
150
157
|
end
|
|
@@ -165,7 +172,7 @@ module Async
|
|
|
165
172
|
name ||= UNNAMED
|
|
166
173
|
|
|
167
174
|
if mark?(key)
|
|
168
|
-
Console.debug(self
|
|
175
|
+
Console.debug(self, "Reusing existing child.", child: {key: key, name: name})
|
|
169
176
|
return false
|
|
170
177
|
end
|
|
171
178
|
|
|
@@ -173,15 +180,20 @@ module Async
|
|
|
173
180
|
|
|
174
181
|
fiber do
|
|
175
182
|
while @running
|
|
176
|
-
child
|
|
183
|
+
Console.debug(self, "Starting child...", child: {key: key, name: name, restart: restart, health_check_timeout: health_check_timeout}, statistics: @statistics)
|
|
177
184
|
|
|
185
|
+
child = self.start(name, &block)
|
|
178
186
|
state = insert(key, child)
|
|
179
187
|
|
|
188
|
+
Console.debug(self, "Started child.", child: child, spawn: {key: key, restart: restart, health_check_timeout: health_check_timeout}, statistics: @statistics)
|
|
189
|
+
|
|
180
190
|
# 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.
|
|
181
191
|
if health_check_timeout
|
|
182
192
|
age_clock = state[:age] = Clock.start
|
|
183
193
|
end
|
|
184
194
|
|
|
195
|
+
status = nil
|
|
196
|
+
|
|
185
197
|
begin
|
|
186
198
|
status = @group.wait_for(child) do |message|
|
|
187
199
|
case message
|
|
@@ -194,15 +206,17 @@ module Async
|
|
|
194
206
|
age_clock&.reset!
|
|
195
207
|
end
|
|
196
208
|
end
|
|
209
|
+
rescue => error
|
|
210
|
+
Console.error(self, "Error during child process management!", exception: error, running: @running)
|
|
197
211
|
ensure
|
|
198
212
|
delete(key, child)
|
|
199
213
|
end
|
|
200
214
|
|
|
201
|
-
if status
|
|
202
|
-
Console.debug(self
|
|
215
|
+
if status&.success?
|
|
216
|
+
Console.debug(self, "Child exited successfully.", status: status, running: @running)
|
|
203
217
|
else
|
|
204
218
|
@statistics.failure!
|
|
205
|
-
Console.error(self, status: status)
|
|
219
|
+
Console.error(self, "Child exited with error!", status: status, running: @running)
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
if restart
|
|
@@ -100,9 +100,14 @@ module Async
|
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
private def each_running(&block)
|
|
104
|
+
# We create a copy of the values here, in case the block modifies the running set:
|
|
105
|
+
@running.values.each(&block)
|
|
106
|
+
end
|
|
107
|
+
|
|
103
108
|
# Perform a health check on all running processes.
|
|
104
109
|
def health_check!
|
|
105
|
-
|
|
110
|
+
each_running do |fiber|
|
|
106
111
|
fiber.resume(:health_check!)
|
|
107
112
|
end
|
|
108
113
|
end
|
|
@@ -111,7 +116,7 @@ module Async
|
|
|
111
116
|
# This resumes the controlling fiber with an instance of {Interrupt}.
|
|
112
117
|
def interrupt
|
|
113
118
|
Console.info(self, "Sending interrupt to #{@running.size} running processes...")
|
|
114
|
-
|
|
119
|
+
each_running do |fiber|
|
|
115
120
|
fiber.resume(Interrupt)
|
|
116
121
|
end
|
|
117
122
|
end
|
|
@@ -120,7 +125,7 @@ module Async
|
|
|
120
125
|
# This resumes the controlling fiber with an instance of {Terminate}.
|
|
121
126
|
def terminate
|
|
122
127
|
Console.info(self, "Sending terminate to #{@running.size} running processes...")
|
|
123
|
-
|
|
128
|
+
each_running do |fiber|
|
|
124
129
|
fiber.resume(Terminate)
|
|
125
130
|
end
|
|
126
131
|
end
|
|
@@ -129,7 +134,7 @@ module Async
|
|
|
129
134
|
# This resumes the controlling fiber with an instance of {Kill}.
|
|
130
135
|
def kill
|
|
131
136
|
Console.info(self, "Sending kill to #{@running.size} running processes...")
|
|
132
|
-
|
|
137
|
+
each_running do |fiber|
|
|
133
138
|
fiber.resume(Kill)
|
|
134
139
|
end
|
|
135
140
|
end
|
|
@@ -183,7 +188,7 @@ module Async
|
|
|
183
188
|
self.wait_for_exit(clock, interrupt_timeout)
|
|
184
189
|
end
|
|
185
190
|
|
|
186
|
-
if terminate_timeout
|
|
191
|
+
if terminate_timeout and self.any?
|
|
187
192
|
clock = Async::Clock.start
|
|
188
193
|
|
|
189
194
|
# If the children are still running, terminate them:
|
|
@@ -231,8 +236,8 @@ module Async
|
|
|
231
236
|
protected
|
|
232
237
|
|
|
233
238
|
def wait_for_children(duration = nil)
|
|
234
|
-
# This log is a
|
|
235
|
-
|
|
239
|
+
# This log is a bit noisy and doesn't really provide a lot of useful information:
|
|
240
|
+
Console.debug(self, "Waiting for children...", duration: duration, running: @running)
|
|
236
241
|
|
|
237
242
|
unless @running.empty?
|
|
238
243
|
# Maybe consider using a proper event loop here:
|
|
@@ -63,9 +63,9 @@ module Async
|
|
|
63
63
|
# Formats the message using JSON and sends it to the parent controller.
|
|
64
64
|
# This is suitable for use with {Channel}.
|
|
65
65
|
def send(**message)
|
|
66
|
-
data = ::JSON.dump(message)
|
|
66
|
+
data = ::JSON.dump(message) << "\n"
|
|
67
67
|
|
|
68
|
-
@io.
|
|
68
|
+
@io.write(data)
|
|
69
69
|
@io.flush
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -56,6 +56,24 @@ module Async
|
|
|
56
56
|
@restarts += other.restarts
|
|
57
57
|
@failures += other.failures
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
# Generate a hash representation of the statistics.
|
|
61
|
+
#
|
|
62
|
+
# @returns [Hash] The statistics as a hash.
|
|
63
|
+
def as_json(...)
|
|
64
|
+
{
|
|
65
|
+
spawns: @spawns,
|
|
66
|
+
restarts: @restarts,
|
|
67
|
+
failures: @failures,
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Generate a JSON representation of the statistics.
|
|
72
|
+
#
|
|
73
|
+
# @returns [String] The statistics as JSON.
|
|
74
|
+
def to_json(...)
|
|
75
|
+
as_json.to_json(...)
|
|
76
|
+
end
|
|
59
77
|
end
|
|
60
78
|
end
|
|
61
79
|
end
|
data/license.md
CHANGED
|
@@ -5,6 +5,7 @@ Copyright, 2019, by Yuji Yaginuma.
|
|
|
5
5
|
Copyright, 2020, by Olle Jonsson.
|
|
6
6
|
Copyright, 2020, by Juan Antonio Martín Lucas.
|
|
7
7
|
Copyright, 2022, by Anton Sozontov.
|
|
8
|
+
Copyright, 2025, by Marc-André Cournoyer.
|
|
8
9
|
|
|
9
10
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
11
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
|
@@ -26,6 +26,23 @@ Please see the [project documentation](https://socketry.github.io/async-containe
|
|
|
26
26
|
|
|
27
27
|
Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases.
|
|
28
28
|
|
|
29
|
+
### v0.27.5
|
|
30
|
+
|
|
31
|
+
- Make the child handling more robust in the face of exceptions.
|
|
32
|
+
|
|
33
|
+
### v0.27.4
|
|
34
|
+
|
|
35
|
+
- Fix race condition where `wait_for` could modify `@running` while it was being iterated over (`each_value`) during health checks.
|
|
36
|
+
|
|
37
|
+
### v0.27.3
|
|
38
|
+
|
|
39
|
+
- Add log for starting child, including container statistics.
|
|
40
|
+
- Don't try to (log) "terminate 0 child processes" if there are none.
|
|
41
|
+
|
|
42
|
+
### v0.27.2
|
|
43
|
+
|
|
44
|
+
- More logging, especially around failure cases.
|
|
45
|
+
|
|
29
46
|
### v0.27.1
|
|
30
47
|
|
|
31
48
|
- Log caller and timeout when waiting on a child instance to exit, if it blocks.
|
|
@@ -51,21 +68,6 @@ Please see the [project releases](https://socketry.github.io/async-container/rel
|
|
|
51
68
|
|
|
52
69
|
- [Add support for `NOTIFY_LOG` for Kubernetes readiness probes.](https://socketry.github.io/async-container/releases/index#add-support-for-notify_log-for-kubernetes-readiness-probes.)
|
|
53
70
|
|
|
54
|
-
### v0.21.0
|
|
55
|
-
|
|
56
|
-
- 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.
|
|
57
|
-
|
|
58
|
-
### v0.20.1
|
|
59
|
-
|
|
60
|
-
- Fix compatibility between <code class="language-ruby">Async::Container::Hybrid</code> and the health check.
|
|
61
|
-
- <code class="language-ruby">Async::Container::Generic\#initialize</code> passes unused arguments through to <code class="language-ruby">Async::Container::Group</code>.
|
|
62
|
-
|
|
63
|
-
### v0.20.0
|
|
64
|
-
|
|
65
|
-
- Improve container signal handling reliability by using `Thread.handle_interrupt` except at known safe points.
|
|
66
|
-
- Improved logging when child process fails and container startup.
|
|
67
|
-
- [Add `health_check_timeout` for detecting hung processes.](https://socketry.github.io/async-container/releases/index#add-health_check_timeout-for-detecting-hung-processes.)
|
|
68
|
-
|
|
69
71
|
## Contributing
|
|
70
72
|
|
|
71
73
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.27.5
|
|
4
|
+
|
|
5
|
+
- Make the child handling more robust in the face of exceptions.
|
|
6
|
+
|
|
7
|
+
## v0.27.4
|
|
8
|
+
|
|
9
|
+
- Fix race condition where `wait_for` could modify `@running` while it was being iterated over (`each_value`) during health checks.
|
|
10
|
+
|
|
11
|
+
## v0.27.3
|
|
12
|
+
|
|
13
|
+
- Add log for starting child, including container statistics.
|
|
14
|
+
- Don't try to (log) "terminate 0 child processes" if there are none.
|
|
15
|
+
|
|
16
|
+
## v0.27.2
|
|
17
|
+
|
|
18
|
+
- More logging, especially around failure cases.
|
|
19
|
+
|
|
3
20
|
## v0.27.1
|
|
4
21
|
|
|
5
22
|
- Log caller and timeout when waiting on a child instance to exit, if it blocks.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-container
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.27.
|
|
4
|
+
version: 0.27.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
8
8
|
- Olle Jonsson
|
|
9
9
|
- Anton Sozontov
|
|
10
10
|
- Juan Antonio Martín Lucas
|
|
11
|
+
- Marc-André Cournoyer
|
|
11
12
|
- Yuji Yaginuma
|
|
12
13
|
bindir: bin
|
|
13
14
|
cert_chain:
|
|
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
105
106
|
- !ruby/object:Gem::Version
|
|
106
107
|
version: '0'
|
|
107
108
|
requirements: []
|
|
108
|
-
rubygems_version: 3.
|
|
109
|
+
rubygems_version: 3.7.2
|
|
109
110
|
specification_version: 4
|
|
110
111
|
summary: Abstract container-based parallelism using threads and processes where appropriate.
|
|
111
112
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|