async-container 0.18.3 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|