async-container 0.18.3 → 0.19.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/container/best.rb +4 -4
- data/lib/async/container/channel.rb +2 -2
- data/lib/async/container/controller.rb +57 -44
- data/lib/async/container/error.rb +4 -4
- data/lib/async/container/forked.rb +186 -4
- data/lib/async/container/generic.rb +23 -14
- data/lib/async/container/group.rb +21 -10
- data/lib/async/container/hybrid.rb +3 -3
- data/lib/async/container/notify/console.rb +4 -4
- data/lib/async/container/notify/pipe.rb +5 -5
- data/lib/async/container/notify/server.rb +14 -7
- data/lib/async/container/notify/socket.rb +8 -4
- data/lib/async/container/notify.rb +4 -4
- data/lib/async/container/statistics.rb +2 -2
- data/lib/async/container/threaded.rb +196 -4
- data/lib/async/container/version.rb +1 -1
- data/lib/async/container.rb +2 -2
- data/license.md +1 -1
- data/releases.md +6 -0
- data.tar.gz.sig +0 -0
- metadata +4 -10
- metadata.gz.sig +0 -0
- data/lib/async/container/process.rb +0 -172
- data/lib/async/container/thread.rb +0 -199
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2edeb6fa679f328736bb4a65c3b342b4aea94f2fc79557d4fc6e9e84bee77a2e
|
4
|
+
data.tar.gz: 5c1c52a4f90c8710efebb411d97e5a8342ea99d1bbe09082deed562b8acdce99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee365ff16248e3064b136cdfeddec8e0f01ef77decbd514b94fc4a3bce1a38c2c51ffa5ba1e071b2fcd6bad6b7c4d4e322f26e8e5a530e4f45a68266d67429da
|
7
|
+
data.tar.gz: 171a93c5855cad3a112622232bbc7e93b880d4c77223cb8a17c7772a0687a0be40af3143a0ecad2b14a9e9aa656010cdf47eb42f80e14b08148cd02f4a4eaa6c
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/container/best.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
6
|
+
require_relative "forked"
|
7
|
+
require_relative "threaded"
|
8
|
+
require_relative "hybrid"
|
9
9
|
|
10
10
|
module Async
|
11
11
|
module Container
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
4
|
+
# Copyright, 2018-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
6
|
+
require_relative "error"
|
7
|
+
require_relative "best"
|
8
8
|
|
9
|
-
require_relative
|
10
|
-
require_relative
|
9
|
+
require_relative "statistics"
|
10
|
+
require_relative "notify"
|
11
11
|
|
12
12
|
module Async
|
13
13
|
module Container
|
@@ -26,13 +26,10 @@ module Async
|
|
26
26
|
@container = nil
|
27
27
|
@container_class = container_class
|
28
28
|
|
29
|
-
|
30
|
-
@notify.status!("Initializing...")
|
31
|
-
end
|
32
|
-
|
29
|
+
@notify = notify
|
33
30
|
@signals = {}
|
34
31
|
|
35
|
-
trap(SIGHUP) do
|
32
|
+
self.trap(SIGHUP) do
|
36
33
|
self.restart
|
37
34
|
end
|
38
35
|
|
@@ -93,7 +90,12 @@ module Async
|
|
93
90
|
|
94
91
|
# Start the container unless it's already running.
|
95
92
|
def start
|
96
|
-
|
93
|
+
unless @container
|
94
|
+
Console.info(self) {"Controller starting..."}
|
95
|
+
self.restart
|
96
|
+
end
|
97
|
+
|
98
|
+
Console.info(self) {"Controller started..."}
|
97
99
|
end
|
98
100
|
|
99
101
|
# Stop the container if it's running.
|
@@ -109,9 +111,9 @@ module Async
|
|
109
111
|
if @container
|
110
112
|
@notify&.restarting!
|
111
113
|
|
112
|
-
Console.
|
114
|
+
Console.debug(self) {"Restarting container..."}
|
113
115
|
else
|
114
|
-
Console.
|
116
|
+
Console.debug(self) {"Starting container..."}
|
115
117
|
end
|
116
118
|
|
117
119
|
container = self.create_container
|
@@ -125,9 +127,9 @@ module Async
|
|
125
127
|
end
|
126
128
|
|
127
129
|
# Wait for all child processes to enter the ready state.
|
128
|
-
Console.
|
130
|
+
Console.debug(self, "Waiting for startup...")
|
129
131
|
container.wait_until_ready
|
130
|
-
Console.
|
132
|
+
Console.debug(self, "Finished startup.")
|
131
133
|
|
132
134
|
if container.failed?
|
133
135
|
@notify&.error!("Container failed to start!")
|
@@ -143,7 +145,7 @@ module Async
|
|
143
145
|
container = nil
|
144
146
|
|
145
147
|
if old_container
|
146
|
-
Console.
|
148
|
+
Console.debug(self, "Stopping old container...")
|
147
149
|
old_container&.stop(@graceful_stop)
|
148
150
|
end
|
149
151
|
|
@@ -157,7 +159,7 @@ module Async
|
|
157
159
|
def reload
|
158
160
|
@notify&.reloading!
|
159
161
|
|
160
|
-
Console.
|
162
|
+
Console.info(self) {"Reloading container: #{@container}..."}
|
161
163
|
|
162
164
|
begin
|
163
165
|
self.setup(@container)
|
@@ -166,11 +168,11 @@ module Async
|
|
166
168
|
end
|
167
169
|
|
168
170
|
# Wait for all child processes to enter the ready state.
|
169
|
-
Console.
|
171
|
+
Console.debug(self, "Waiting for startup...")
|
170
172
|
|
171
173
|
@container.wait_until_ready
|
172
174
|
|
173
|
-
Console.
|
175
|
+
Console.debug(self, "Finished startup.")
|
174
176
|
|
175
177
|
if @container.failed?
|
176
178
|
@notify.error!("Container failed to reload!")
|
@@ -183,10 +185,41 @@ module Async
|
|
183
185
|
|
184
186
|
# Enter the controller run loop, trapping `SIGINT` and `SIGTERM`.
|
185
187
|
def run
|
188
|
+
@notify&.status!("Initializing...")
|
189
|
+
|
190
|
+
with_signal_handlers do
|
191
|
+
self.start
|
192
|
+
|
193
|
+
while @container&.running?
|
194
|
+
begin
|
195
|
+
@container.wait
|
196
|
+
rescue SignalException => exception
|
197
|
+
if handler = @signals[exception.signo]
|
198
|
+
begin
|
199
|
+
handler.call
|
200
|
+
rescue SetupError => error
|
201
|
+
Console.error(self, error)
|
202
|
+
end
|
203
|
+
else
|
204
|
+
raise
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
rescue Interrupt
|
210
|
+
self.stop
|
211
|
+
rescue Terminate
|
212
|
+
self.stop(false)
|
213
|
+
ensure
|
214
|
+
self.stop(false)
|
215
|
+
end
|
216
|
+
|
217
|
+
private def with_signal_handlers
|
186
218
|
# I thought this was the default... but it doesn't always raise an exception unless you do this explicitly.
|
187
|
-
|
219
|
+
|
188
220
|
interrupt_action = Signal.trap(:INT) do
|
189
|
-
#
|
221
|
+
# We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly.
|
222
|
+
# $stderr.puts "Received INT signal, interrupting...", caller
|
190
223
|
::Thread.current.raise(Interrupt)
|
191
224
|
end
|
192
225
|
|
@@ -197,33 +230,13 @@ module Async
|
|
197
230
|
|
198
231
|
hangup_action = Signal.trap(:HUP) do
|
199
232
|
# $stderr.puts "Received HUP signal, restarting...", caller
|
200
|
-
::Thread.current.raise(
|
233
|
+
::Thread.current.raise(Restart)
|
201
234
|
end
|
202
235
|
|
203
|
-
|
204
|
-
|
205
|
-
while @container&.running?
|
206
|
-
begin
|
207
|
-
@container.wait
|
208
|
-
rescue SignalException => exception
|
209
|
-
if handler = @signals[exception.signo]
|
210
|
-
begin
|
211
|
-
handler.call
|
212
|
-
rescue SetupError => error
|
213
|
-
Console.logger.error(self) {error}
|
214
|
-
end
|
215
|
-
else
|
216
|
-
raise
|
217
|
-
end
|
218
|
-
end
|
236
|
+
::Thread.handle_interrupt(SignalException => :never) do
|
237
|
+
yield
|
219
238
|
end
|
220
|
-
rescue Interrupt
|
221
|
-
self.stop
|
222
|
-
rescue Terminate
|
223
|
-
self.stop(false)
|
224
239
|
ensure
|
225
|
-
self.stop(false)
|
226
|
-
|
227
240
|
# Restore the interrupt handler:
|
228
241
|
Signal.trap(:INT, interrupt_action)
|
229
242
|
Signal.trap(:TERM, terminate_action)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
module Async
|
7
7
|
module Container
|
@@ -12,15 +12,15 @@ module Async
|
|
12
12
|
|
13
13
|
# Similar to {Interrupt}, but represents `SIGTERM`.
|
14
14
|
class Terminate < SignalException
|
15
|
-
SIGTERM = Signal.list[
|
15
|
+
SIGTERM = Signal.list["TERM"]
|
16
16
|
|
17
17
|
def initialize
|
18
18
|
super(SIGTERM)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
23
|
-
SIGHUP = Signal.list[
|
22
|
+
class Restart < SignalException
|
23
|
+
SIGHUP = Signal.list["HUP"]
|
24
24
|
|
25
25
|
def initialize
|
26
26
|
super(SIGHUP)
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2017-
|
4
|
+
# Copyright, 2017-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
|
6
|
+
require_relative "error"
|
7
|
+
|
8
|
+
require_relative "generic"
|
9
|
+
require_relative "channel"
|
10
|
+
require_relative "notify/pipe"
|
8
11
|
|
9
12
|
module Async
|
10
13
|
module Container
|
@@ -15,11 +18,190 @@ module Async
|
|
15
18
|
true
|
16
19
|
end
|
17
20
|
|
21
|
+
# Represents a running child process from the point of view of the parent container.
|
22
|
+
class Child < Channel
|
23
|
+
# Represents a running child process from the point of view of the child process.
|
24
|
+
class Instance < Notify::Pipe
|
25
|
+
# Wrap an instance around the {Process} instance from within the forked child.
|
26
|
+
# @parameter process [Process] The process intance to wrap.
|
27
|
+
def self.for(process)
|
28
|
+
instance = self.new(process.out)
|
29
|
+
|
30
|
+
# The child process won't be reading from the channel:
|
31
|
+
process.close_read
|
32
|
+
|
33
|
+
instance.name = process.name
|
34
|
+
|
35
|
+
return instance
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(io)
|
39
|
+
super
|
40
|
+
|
41
|
+
@name = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set the process title to the specified value.
|
45
|
+
# @parameter value [String] The name of the process.
|
46
|
+
def name= value
|
47
|
+
if @name = value
|
48
|
+
::Process.setproctitle(@name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# The name of the process.
|
53
|
+
# @returns [String]
|
54
|
+
def name
|
55
|
+
@name
|
56
|
+
end
|
57
|
+
|
58
|
+
# Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}.
|
59
|
+
# This method replaces the child process with the new executable, thus this method never returns.
|
60
|
+
def exec(*arguments, ready: true, **options)
|
61
|
+
if ready
|
62
|
+
self.ready!(status: "(exec)")
|
63
|
+
else
|
64
|
+
self.before_spawn(arguments, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
::Process.exec(*arguments, **options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fork a child process appropriate for a container.
|
72
|
+
# @returns [Process]
|
73
|
+
def self.fork(**options)
|
74
|
+
# $stderr.puts fork: caller
|
75
|
+
self.new(**options) do |process|
|
76
|
+
::Process.fork do
|
77
|
+
# We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly.
|
78
|
+
Signal.trap(:INT) {::Thread.current.raise(Interrupt)}
|
79
|
+
Signal.trap(:TERM) {::Thread.current.raise(Terminate)}
|
80
|
+
Signal.trap(:HUP) {::Thread.current.raise(Restart)}
|
81
|
+
|
82
|
+
# This could be a configuration option:
|
83
|
+
::Thread.handle_interrupt(SignalException => :immediate) do
|
84
|
+
yield Instance.for(process)
|
85
|
+
rescue Interrupt
|
86
|
+
# Graceful exit.
|
87
|
+
rescue Exception => error
|
88
|
+
Console.error(self, error)
|
89
|
+
|
90
|
+
exit!(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.spawn(*arguments, name: nil, **options)
|
97
|
+
self.new(name: name) do |process|
|
98
|
+
Notify::Pipe.new(process.out).before_spawn(arguments, options)
|
99
|
+
|
100
|
+
::Process.spawn(*arguments, **options)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Initialize the process.
|
105
|
+
# @parameter name [String] The name to use for the child process.
|
106
|
+
def initialize(name: nil)
|
107
|
+
super()
|
108
|
+
|
109
|
+
@name = name
|
110
|
+
@status = nil
|
111
|
+
@pid = nil
|
112
|
+
|
113
|
+
@pid = yield(self)
|
114
|
+
|
115
|
+
# The parent process won't be writing to the channel:
|
116
|
+
self.close_write
|
117
|
+
end
|
118
|
+
|
119
|
+
# Set the name of the process.
|
120
|
+
# Invokes {::Process.setproctitle} if invoked in the child process.
|
121
|
+
def name= value
|
122
|
+
@name = value
|
123
|
+
|
124
|
+
# If we are the child process:
|
125
|
+
::Process.setproctitle(@name) if @pid.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
# The name of the process.
|
129
|
+
# @attribute [String]
|
130
|
+
attr :name
|
131
|
+
|
132
|
+
# @attribute [Integer] The process identifier.
|
133
|
+
attr :pid
|
134
|
+
|
135
|
+
# A human readable representation of the process.
|
136
|
+
# @returns [String]
|
137
|
+
def inspect
|
138
|
+
"\#<#{self.class} name=#{@name.inspect} status=#{@status.inspect} pid=#{@pid.inspect}>"
|
139
|
+
end
|
140
|
+
|
141
|
+
alias to_s inspect
|
142
|
+
|
143
|
+
# Invoke {#terminate!} and then {#wait} for the child process to exit.
|
144
|
+
def close
|
145
|
+
self.terminate!
|
146
|
+
self.wait
|
147
|
+
ensure
|
148
|
+
super
|
149
|
+
end
|
150
|
+
|
151
|
+
# Send `SIGINT` to the child process.
|
152
|
+
def interrupt!
|
153
|
+
unless @status
|
154
|
+
::Process.kill(:INT, @pid)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Send `SIGTERM` to the child process.
|
159
|
+
def terminate!
|
160
|
+
unless @status
|
161
|
+
::Process.kill(:TERM, @pid)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Send `SIGHUP` to the child process.
|
166
|
+
def restart!
|
167
|
+
unless @status
|
168
|
+
::Process.kill(:HUP, @pid)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Wait for the child process to exit.
|
173
|
+
# @asynchronous This method may block.
|
174
|
+
#
|
175
|
+
# @returns [::Process::Status] The process exit status.
|
176
|
+
def wait
|
177
|
+
if @pid && @status.nil?
|
178
|
+
Console.debug(self, "Waiting for process to exit...", pid: @pid)
|
179
|
+
|
180
|
+
_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
|
181
|
+
|
182
|
+
while @status.nil?
|
183
|
+
sleep(0.1)
|
184
|
+
|
185
|
+
_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
|
186
|
+
|
187
|
+
if @status.nil?
|
188
|
+
Console.warn(self) {"Process #{@pid} is blocking, has it exited?"}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
Console.debug(self, "Process exited.", pid: @pid, status: @status)
|
194
|
+
|
195
|
+
return @status
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
18
200
|
# Start a named child process and execute the provided block in it.
|
19
201
|
# @parameter name [String] The name (title) of the child process.
|
20
202
|
# @parameter block [Proc] The block to execute in the child process.
|
21
203
|
def start(name, &block)
|
22
|
-
|
204
|
+
Child.fork(name: name, &block)
|
23
205
|
end
|
24
206
|
end
|
25
207
|
end
|
@@ -3,18 +3,16 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require
|
6
|
+
require "etc"
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
require_relative
|
11
|
-
require_relative 'keyed'
|
12
|
-
require_relative 'statistics'
|
8
|
+
require_relative "group"
|
9
|
+
require_relative "keyed"
|
10
|
+
require_relative "statistics"
|
13
11
|
|
14
12
|
module Async
|
15
13
|
module Container
|
16
14
|
# An environment variable key to override {.processor_count}.
|
17
|
-
ASYNC_CONTAINER_PROCESSOR_COUNT =
|
15
|
+
ASYNC_CONTAINER_PROCESSOR_COUNT = "ASYNC_CONTAINER_PROCESSOR_COUNT"
|
18
16
|
|
19
17
|
# The processor count which may be used for the default number of container threads/processes. You can override the value provided by the system by specifying the `ASYNC_CONTAINER_PROCESSOR_COUNT` environment variable.
|
20
18
|
# @returns [Integer] The number of hardware processors which can run threads/processes simultaneously.
|
@@ -104,16 +102,23 @@ module Async
|
|
104
102
|
# @returns [Boolean] The children all became ready.
|
105
103
|
def wait_until_ready
|
106
104
|
while true
|
107
|
-
Console.
|
105
|
+
Console.debug(self) do |buffer|
|
108
106
|
buffer.puts "Waiting for ready:"
|
109
107
|
@state.each do |child, state|
|
110
|
-
buffer.puts "\t#{child.
|
108
|
+
buffer.puts "\t#{child.inspect}: #{state}"
|
111
109
|
end
|
112
110
|
end
|
113
111
|
|
114
112
|
self.sleep
|
115
113
|
|
116
114
|
if self.status?(:ready)
|
115
|
+
Console.logger.debug(self) do |buffer|
|
116
|
+
buffer.puts "All ready:"
|
117
|
+
@state.each do |child, state|
|
118
|
+
buffer.puts "\t#{child.inspect}: #{state}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
117
122
|
return true
|
118
123
|
end
|
119
124
|
end
|
@@ -126,7 +131,7 @@ module Async
|
|
126
131
|
@group.stop(timeout)
|
127
132
|
|
128
133
|
if @group.running?
|
129
|
-
Console.
|
134
|
+
Console.warn(self) {"Group is still running after stopping it!"}
|
130
135
|
end
|
131
136
|
ensure
|
132
137
|
@running = true
|
@@ -140,7 +145,7 @@ module Async
|
|
140
145
|
name ||= UNNAMED
|
141
146
|
|
142
147
|
if mark?(key)
|
143
|
-
Console.
|
148
|
+
Console.debug(self) {"Reusing existing child for #{key}: #{name}"}
|
144
149
|
return false
|
145
150
|
end
|
146
151
|
|
@@ -161,10 +166,10 @@ module Async
|
|
161
166
|
end
|
162
167
|
|
163
168
|
if status.success?
|
164
|
-
Console.
|
169
|
+
Console.debug(self) {"#{child} exited with #{status}"}
|
165
170
|
else
|
166
171
|
@statistics.failure!
|
167
|
-
Console.
|
172
|
+
Console.error(self, status: status)
|
168
173
|
end
|
169
174
|
|
170
175
|
if restart
|
@@ -190,8 +195,12 @@ module Async
|
|
190
195
|
|
191
196
|
# @deprecated Please use {spawn} or {run} instead.
|
192
197
|
def async(**options, &block)
|
198
|
+
# warn "#{self.class}##{__method__} is deprecated, please use `spawn` or `run` instead.", uplevel: 1
|
199
|
+
|
200
|
+
require "async"
|
201
|
+
|
193
202
|
spawn(**options) do |instance|
|
194
|
-
Async
|
203
|
+
Async(instance, &block)
|
195
204
|
end
|
196
205
|
end
|
197
206
|
|
@@ -3,10 +3,10 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
6
|
+
require "fiber"
|
7
|
+
require "async/clock"
|
8
8
|
|
9
|
-
require_relative
|
9
|
+
require_relative "error"
|
10
10
|
|
11
11
|
module Async
|
12
12
|
module Container
|
@@ -65,7 +65,7 @@ module Async
|
|
65
65
|
# Interrupt all running processes.
|
66
66
|
# This resumes the controlling fiber with an instance of {Interrupt}.
|
67
67
|
def interrupt
|
68
|
-
Console.
|
68
|
+
Console.info(self, "Sending interrupt to #{@running.size} running processes...")
|
69
69
|
@running.each_value do |fiber|
|
70
70
|
fiber.resume(Interrupt)
|
71
71
|
end
|
@@ -74,7 +74,7 @@ module Async
|
|
74
74
|
# Terminate all running processes.
|
75
75
|
# This resumes the controlling fiber with an instance of {Terminate}.
|
76
76
|
def terminate
|
77
|
-
Console.
|
77
|
+
Console.info(self, "Sending terminate to #{@running.size} running processes...")
|
78
78
|
@running.each_value do |fiber|
|
79
79
|
fiber.resume(Terminate)
|
80
80
|
end
|
@@ -83,6 +83,7 @@ module Async
|
|
83
83
|
# Stop all child processes using {#terminate}.
|
84
84
|
# @parameter timeout [Boolean | Numeric | Nil] If specified, invoke a graceful shutdown using {#interrupt} first.
|
85
85
|
def stop(timeout = 1)
|
86
|
+
Console.info(self, "Stopping all processes...", timeout: timeout)
|
86
87
|
# Use a default timeout if not specified:
|
87
88
|
timeout = 1 if timeout == true
|
88
89
|
|
@@ -105,7 +106,7 @@ module Async
|
|
105
106
|
end
|
106
107
|
|
107
108
|
# Terminate all children:
|
108
|
-
self.terminate
|
109
|
+
self.terminate if any?
|
109
110
|
|
110
111
|
# Wait for all children to exit:
|
111
112
|
self.wait
|
@@ -137,14 +138,24 @@ module Async
|
|
137
138
|
protected
|
138
139
|
|
139
140
|
def wait_for_children(duration = nil)
|
140
|
-
Console.debug(self, "Waiting for children...", duration: duration)
|
141
|
+
Console.debug(self, "Waiting for children...", duration: duration, running: @running)
|
142
|
+
|
141
143
|
if !@running.empty?
|
142
144
|
# Maybe consider using a proper event loop here:
|
145
|
+
if ready = self.select(duration)
|
146
|
+
ready.each do |io|
|
147
|
+
@running[io].resume
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Wait for a child process to exit OR a signal to be received.
|
154
|
+
def select(duration)
|
155
|
+
::Thread.handle_interrupt(SignalException => :immediate) do
|
143
156
|
readable, _, _ = ::IO.select(@running.keys, nil, nil, duration)
|
144
157
|
|
145
|
-
readable
|
146
|
-
@running[io].resume
|
147
|
-
end
|
158
|
+
return readable
|
148
159
|
end
|
149
160
|
end
|
150
161
|
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2022, by Anton Sozontov.
|
6
6
|
|
7
|
-
require_relative
|
8
|
-
require_relative
|
7
|
+
require_relative "forked"
|
8
|
+
require_relative "threaded"
|
9
9
|
|
10
10
|
module Async
|
11
11
|
module Container
|
@@ -3,9 +3,9 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2020-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "client"
|
7
7
|
|
8
|
-
require
|
8
|
+
require "console"
|
9
9
|
|
10
10
|
module Async
|
11
11
|
module Container
|
@@ -24,8 +24,8 @@ module Async
|
|
24
24
|
end
|
25
25
|
|
26
26
|
# Send a message to the console.
|
27
|
-
def send(level: :
|
28
|
-
@logger.
|
27
|
+
def send(level: :info, **message)
|
28
|
+
@logger.public_send(level, self) {message}
|
29
29
|
end
|
30
30
|
|
31
31
|
# Send an error message to the console.
|