async-container 0.16.6 → 0.16.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/container.rb +0 -1
- data/lib/async/container/best.rb +9 -3
- data/lib/async/container/channel.rb +13 -0
- data/lib/async/container/controller.rb +32 -16
- data/lib/async/container/error.rb +13 -0
- data/lib/async/container/forked.rb +5 -1
- data/lib/async/container/generic.rb +39 -10
- data/lib/async/container/group.rb +17 -2
- data/lib/async/container/hybrid.rb +5 -0
- data/lib/async/container/keyed.rb +12 -0
- data/lib/async/container/notify.rb +4 -5
- data/lib/async/container/notify/client.rb +16 -1
- data/lib/async/container/notify/console.rb +8 -21
- data/lib/async/container/notify/pipe.rb +7 -5
- data/lib/async/container/notify/server.rb +0 -1
- data/lib/async/container/notify/socket.rb +14 -1
- data/lib/async/container/process.rb +26 -0
- data/lib/async/container/statistics.rb +16 -0
- data/lib/async/container/thread.rb +40 -3
- data/lib/async/container/threaded.rb +5 -1
- data/lib/async/container/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87218f3196f6535fbb3849b6cb24ee3cc4ba6f59ed3c55ca9374b2889d51aa62
|
4
|
+
data.tar.gz: 3b38ac9e91231d51b0d29d7fab36c5cdae70df84833aa97307d6e4e9462b405d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c0c01247d61f0cdc5f5cf7aa404d2115ffecf853c1c33669dbd25dbed4d7e6f0db20495ffcf6052497156cfd16b41d7ec8319f7b57ee0ba6fda8aa799d28aa3
|
7
|
+
data.tar.gz: 6fa64afb47c7f8a83e79773ed9ca891966bf88e5ba092e304f859ffc2a76cd0525830f96ece65d9b1ca5637fc3527aa1750ff93b2374d807eca7642f68b73714
|
data/lib/async/container.rb
CHANGED
@@ -23,7 +23,6 @@
|
|
23
23
|
require_relative 'container/controller'
|
24
24
|
|
25
25
|
module Async
|
26
|
-
# Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete.
|
27
26
|
module Container
|
28
27
|
end
|
29
28
|
end
|
data/lib/async/container/best.rb
CHANGED
@@ -25,12 +25,16 @@ require_relative 'threaded'
|
|
25
25
|
require_relative 'hybrid'
|
26
26
|
|
27
27
|
module Async
|
28
|
-
# Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete.
|
29
28
|
module Container
|
29
|
+
# Whether the underlying process supports fork.
|
30
|
+
# @returns [Boolean]
|
30
31
|
def self.fork?
|
31
32
|
::Process.respond_to?(:fork) && ::Process.respond_to?(:setpgid)
|
32
33
|
end
|
33
34
|
|
35
|
+
# Determins the best container class based on the underlying Ruby implementation.
|
36
|
+
# Some platforms, including JRuby, don't support fork. Applications which just want a reasonable default can use this method.
|
37
|
+
# @returns [Class]
|
34
38
|
def self.best_container_class
|
35
39
|
if fork?
|
36
40
|
return Forked
|
@@ -39,8 +43,10 @@ module Async
|
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
42
|
-
|
43
|
-
|
46
|
+
# Create an instance of the best container class.
|
47
|
+
# @returns [Generic] Typically an instance of either {Forked} or {Threaded} containers.
|
48
|
+
def self.new(*arguments, **options)
|
49
|
+
best_container_class.new(*arguments, **options)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
@@ -24,27 +24,40 @@ require 'json'
|
|
24
24
|
|
25
25
|
module Async
|
26
26
|
module Container
|
27
|
+
# Provides a basic multi-thread/multi-process uni-directional communication channel.
|
27
28
|
class Channel
|
29
|
+
# Initialize the channel using a pipe.
|
28
30
|
def initialize
|
29
31
|
@in, @out = ::IO.pipe
|
30
32
|
end
|
31
33
|
|
34
|
+
# The input end of the pipe.
|
35
|
+
# @attribute [IO]
|
32
36
|
attr :in
|
37
|
+
|
38
|
+
# The output end of the pipe.
|
39
|
+
# @attribute [IO]
|
33
40
|
attr :out
|
34
41
|
|
42
|
+
# Close the input end of the pipe.
|
35
43
|
def close_read
|
36
44
|
@in.close
|
37
45
|
end
|
38
46
|
|
47
|
+
# Close the output end of the pipe.
|
39
48
|
def close_write
|
40
49
|
@out.close
|
41
50
|
end
|
42
51
|
|
52
|
+
# Close both ends of the pipe.
|
43
53
|
def close
|
44
54
|
close_read
|
45
55
|
close_write
|
46
56
|
end
|
47
57
|
|
58
|
+
# Receive an object from the pipe.
|
59
|
+
# Internally, prefers to receive newline formatted JSON, otherwise returns a hash table with a single key `:line` which contains the line of data that could not be parsed as JSON.
|
60
|
+
# @returns [Hash]
|
48
61
|
def receive
|
49
62
|
if data = @in.gets
|
50
63
|
begin
|
@@ -28,17 +28,8 @@ require_relative 'notify'
|
|
28
28
|
|
29
29
|
module Async
|
30
30
|
module Container
|
31
|
-
|
32
|
-
|
33
|
-
super("Could not create container!")
|
34
|
-
|
35
|
-
@container = container
|
36
|
-
end
|
37
|
-
|
38
|
-
attr :container
|
39
|
-
end
|
40
|
-
|
41
|
-
# Manages the life-cycle of a container.
|
31
|
+
# Manages the life-cycle of one or more containers in order to support a persistent system.
|
32
|
+
# e.g. a web server, job server or some other long running system.
|
42
33
|
class Controller
|
43
34
|
SIGHUP = Signal.list["HUP"]
|
44
35
|
SIGINT = Signal.list["INT"]
|
@@ -46,6 +37,8 @@ module Async
|
|
46
37
|
SIGUSR1 = Signal.list["USR1"]
|
47
38
|
SIGUSR2 = Signal.list["USR2"]
|
48
39
|
|
40
|
+
# Initialize the controller.
|
41
|
+
# @parameter notify [Notify::Client] A client used for process readiness notifications.
|
49
42
|
def initialize(notify: Notify.open!)
|
50
43
|
@container = nil
|
51
44
|
|
@@ -60,6 +53,8 @@ module Async
|
|
60
53
|
end
|
61
54
|
end
|
62
55
|
|
56
|
+
# The state of the controller.
|
57
|
+
# @returns [String]
|
63
58
|
def state_string
|
64
59
|
if running?
|
65
60
|
"running"
|
@@ -68,42 +63,61 @@ module Async
|
|
68
63
|
end
|
69
64
|
end
|
70
65
|
|
66
|
+
# A human readable representation of the controller.
|
67
|
+
# @returns [String]
|
71
68
|
def to_s
|
72
69
|
"#{self.class} #{state_string}"
|
73
70
|
end
|
74
71
|
|
72
|
+
# Trap the specified signal.
|
73
|
+
# @parameters signal [Symbol] The signal to trap, e.g. `:INT`.
|
74
|
+
# @parameters block [Proc] The signal handler to invoke.
|
75
75
|
def trap(signal, &block)
|
76
76
|
@signals[signal] = block
|
77
77
|
end
|
78
78
|
|
79
|
+
# The current container being managed by the controller.
|
79
80
|
attr :container
|
80
81
|
|
82
|
+
# Create a container for the controller.
|
83
|
+
# Can be overridden by a sub-class.
|
84
|
+
# @returns [Generic] A specific container instance to use.
|
81
85
|
def create_container
|
82
86
|
Container.new
|
83
87
|
end
|
84
88
|
|
89
|
+
# Whether the controller has a running container.
|
90
|
+
# @returns [Boolean]
|
85
91
|
def running?
|
86
92
|
!!@container
|
87
93
|
end
|
88
94
|
|
95
|
+
# Wait for the underlying container to start.
|
89
96
|
def wait
|
90
97
|
@container&.wait
|
91
98
|
end
|
92
99
|
|
100
|
+
# Spawn container instances into the given container.
|
101
|
+
# Should be overridden by a sub-class.
|
102
|
+
# @parameter container [Generic] The container, generally from {#create_container}.
|
93
103
|
def setup(container)
|
94
104
|
# Don't do this, otherwise calling super is risky for sub-classes:
|
95
105
|
# raise NotImplementedError, "Container setup is must be implemented in derived class!"
|
96
106
|
end
|
97
107
|
|
108
|
+
# Start the container unless it's already running.
|
98
109
|
def start
|
99
110
|
self.restart unless @container
|
100
111
|
end
|
101
112
|
|
113
|
+
# Stop the container if it's running.
|
114
|
+
# @parameter graceful [Boolean] Whether to give the children instances time to shut down or to kill them immediately.
|
102
115
|
def stop(graceful = true)
|
103
116
|
@container&.stop(graceful)
|
104
117
|
@container = nil
|
105
118
|
end
|
106
119
|
|
120
|
+
# Restart the container. A new container is created, and if successful, any old container is terminated gracefully.
|
107
121
|
def restart
|
108
122
|
if @container
|
109
123
|
@notify&.restarting!
|
@@ -120,7 +134,7 @@ module Async
|
|
120
134
|
rescue
|
121
135
|
@notify&.error!($!.to_s)
|
122
136
|
|
123
|
-
raise
|
137
|
+
raise SetupError, container
|
124
138
|
end
|
125
139
|
|
126
140
|
# Wait for all child processes to enter the ready state.
|
@@ -133,7 +147,7 @@ module Async
|
|
133
147
|
|
134
148
|
container.stop
|
135
149
|
|
136
|
-
raise
|
150
|
+
raise SetupError, container
|
137
151
|
end
|
138
152
|
|
139
153
|
# Make this swap as atomic as possible:
|
@@ -150,6 +164,7 @@ module Async
|
|
150
164
|
raise
|
151
165
|
end
|
152
166
|
|
167
|
+
# Reload the existing container. Children instances will be reloaded using `SIGHUP`.
|
153
168
|
def reload
|
154
169
|
@notify&.reloading!
|
155
170
|
|
@@ -158,7 +173,7 @@ module Async
|
|
158
173
|
begin
|
159
174
|
self.setup(@container)
|
160
175
|
rescue
|
161
|
-
raise
|
176
|
+
raise SetupError, container
|
162
177
|
end
|
163
178
|
|
164
179
|
# Wait for all child processes to enter the ready state.
|
@@ -169,12 +184,13 @@ module Async
|
|
169
184
|
if @container.failed?
|
170
185
|
@notify.error!("Container failed!")
|
171
186
|
|
172
|
-
raise
|
187
|
+
raise SetupError, @container
|
173
188
|
else
|
174
189
|
@notify&.ready!
|
175
190
|
end
|
176
191
|
end
|
177
192
|
|
193
|
+
# Enter the controller run loop, trapping `SIGINT` and `SIGTERM`.
|
178
194
|
def run
|
179
195
|
# I thought this was the default... but it doesn't always raise an exception unless you do this explicitly.
|
180
196
|
interrupt_action = Signal.trap(:INT) do
|
@@ -194,7 +210,7 @@ module Async
|
|
194
210
|
if handler = @signals[exception.signo]
|
195
211
|
begin
|
196
212
|
handler.call
|
197
|
-
rescue
|
213
|
+
rescue SetupError => error
|
198
214
|
Async.logger.error(self) {error}
|
199
215
|
end
|
200
216
|
else
|
@@ -27,6 +27,7 @@ module Async
|
|
27
27
|
|
28
28
|
Interrupt = ::Interrupt
|
29
29
|
|
30
|
+
# Similar to {Interrupt}, but represents `SIGTERM`.
|
30
31
|
class Terminate < SignalException
|
31
32
|
SIGTERM = Signal.list['TERM']
|
32
33
|
|
@@ -34,5 +35,17 @@ module Async
|
|
34
35
|
super(SIGTERM)
|
35
36
|
end
|
36
37
|
end
|
38
|
+
|
39
|
+
# Represents the error which occured when a container failed to start up correctly.
|
40
|
+
class SetupError < Error
|
41
|
+
def initialize(container)
|
42
|
+
super("Could not create container!")
|
43
|
+
|
44
|
+
@container = container
|
45
|
+
end
|
46
|
+
|
47
|
+
# The container that failed.
|
48
|
+
attr :container
|
49
|
+
end
|
37
50
|
end
|
38
51
|
end
|
@@ -24,13 +24,17 @@ require_relative 'generic'
|
|
24
24
|
require_relative 'process'
|
25
25
|
|
26
26
|
module Async
|
27
|
-
# Manages a reactor within one or more threads.
|
28
27
|
module Container
|
28
|
+
# A multi-process container which uses {Process.fork}.
|
29
29
|
class Forked < Generic
|
30
|
+
# Indicates that this is a multi-process container.
|
30
31
|
def self.multiprocess?
|
31
32
|
true
|
32
33
|
end
|
33
34
|
|
35
|
+
# Start a named child process and execute the provided block in it.
|
36
|
+
# @parameter name [String] The name (title) of the child process.
|
37
|
+
# @parameter block [Proc] The block to execute in the child process.
|
34
38
|
def start(name, &block)
|
35
39
|
Process.fork(name: name, &block)
|
36
40
|
end
|
@@ -30,10 +30,12 @@ require_relative 'statistics'
|
|
30
30
|
|
31
31
|
module Async
|
32
32
|
module Container
|
33
|
+
# An environment variable key to override {.processor_count}.
|
33
34
|
ASYNC_CONTAINER_PROCESSOR_COUNT = 'ASYNC_CONTAINER_PROCESSOR_COUNT'
|
34
35
|
|
35
|
-
# 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.
|
36
|
-
# @
|
36
|
+
# 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.
|
37
|
+
# @returns [Integer] The number of hardware processors which can run threads/processes simultaneously.
|
38
|
+
# @raises [RuntimeError] If the process count is invalid.
|
37
39
|
def self.processor_count(env = ENV)
|
38
40
|
count = env.fetch(ASYNC_CONTAINER_PROCESSOR_COUNT) do
|
39
41
|
Etc.nprocessors rescue 1
|
@@ -46,6 +48,7 @@ module Async
|
|
46
48
|
return count
|
47
49
|
end
|
48
50
|
|
51
|
+
# A base class for implementing containers.
|
49
52
|
class Generic
|
50
53
|
def self.run(*arguments, **options, &block)
|
51
54
|
self.new.run(*arguments, **options, &block)
|
@@ -65,27 +68,35 @@ module Async
|
|
65
68
|
|
66
69
|
attr :state
|
67
70
|
|
71
|
+
# A human readable representation of the container.
|
72
|
+
# @returns [String]
|
68
73
|
def to_s
|
69
74
|
"#{self.class} with #{@statistics.spawns} spawns and #{@statistics.failures} failures."
|
70
75
|
end
|
71
76
|
|
77
|
+
# Look up a child process by key.
|
78
|
+
# A key could be a symbol, a file path, or something else which the child instance represents.
|
72
79
|
def [] key
|
73
80
|
@keyed[key]&.value
|
74
81
|
end
|
75
82
|
|
83
|
+
# Statistics relating to the behavior of children instances.
|
84
|
+
# @attribute [Statistics]
|
76
85
|
attr :statistics
|
77
86
|
|
87
|
+
# Whether any failures have occurred within the container.
|
88
|
+
# @returns [Boolean]
|
78
89
|
def failed?
|
79
90
|
@statistics.failed?
|
80
91
|
end
|
81
92
|
|
82
|
-
# Whether
|
93
|
+
# Whether the container has running children instances.
|
83
94
|
def running?
|
84
95
|
@group.running?
|
85
96
|
end
|
86
97
|
|
87
98
|
# Sleep until some state change occurs.
|
88
|
-
# @
|
99
|
+
# @parameter duration [Numeric] the maximum amount of time to sleep for.
|
89
100
|
def sleep(duration = nil)
|
90
101
|
@group.sleep(duration)
|
91
102
|
end
|
@@ -95,11 +106,17 @@ module Async
|
|
95
106
|
@group.wait
|
96
107
|
end
|
97
108
|
|
109
|
+
# Returns true if all children instances have the specified status flag set.
|
110
|
+
# e.g. `:ready`.
|
111
|
+
# This state is updated by the process readiness protocol mechanism. See {Notify::Client} for more details.
|
112
|
+
# @returns [Boolean]
|
98
113
|
def status?(flag)
|
99
114
|
# This also returns true if all processes have exited/failed:
|
100
115
|
@state.all?{|_, state| state[flag]}
|
101
116
|
end
|
102
117
|
|
118
|
+
# Wait until all the children instances have indicated that they are ready.
|
119
|
+
# @returns [Boolean] The children all became ready.
|
103
120
|
def wait_until_ready
|
104
121
|
while true
|
105
122
|
Async.logger.debug(self) do |buffer|
|
@@ -117,6 +134,8 @@ module Async
|
|
117
134
|
end
|
118
135
|
end
|
119
136
|
|
137
|
+
# Stop the children instances.
|
138
|
+
# @parameter timeout [Boolean | Numeric] Whether to stop gracefully, or a specific timeout.
|
120
139
|
def stop(timeout = true)
|
121
140
|
@running = false
|
122
141
|
@group.stop(timeout)
|
@@ -128,6 +147,10 @@ module Async
|
|
128
147
|
@running = true
|
129
148
|
end
|
130
149
|
|
150
|
+
# Spawn a child instance into the container.
|
151
|
+
# @parameter name [String] The name of the child instance.
|
152
|
+
# @parameter restart [Boolean] Whether to restart the child instance if it fails.
|
153
|
+
# @parameter key [Symbol] A key used for reloading child instances.
|
131
154
|
def spawn(name: nil, restart: false, key: nil, &block)
|
132
155
|
name ||= UNNAMED
|
133
156
|
|
@@ -172,12 +195,8 @@ module Async
|
|
172
195
|
return true
|
173
196
|
end
|
174
197
|
|
175
|
-
|
176
|
-
|
177
|
-
Async::Reactor.run(instance, &block)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
198
|
+
# Run multiple instances of the same block in the container.
|
199
|
+
# @parameter count [Integer] The number of instances to start.
|
181
200
|
def run(count: Container.processor_count, **options, &block)
|
182
201
|
count.times do
|
183
202
|
spawn(**options, &block)
|
@@ -186,6 +205,14 @@ module Async
|
|
186
205
|
return self
|
187
206
|
end
|
188
207
|
|
208
|
+
# @deprecated Please use {spawn} or {run} instead.
|
209
|
+
def async(**options, &block)
|
210
|
+
spawn(**options) do |instance|
|
211
|
+
Async::Reactor.run(instance, &block)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Reload the container's keyed instances.
|
189
216
|
def reload
|
190
217
|
@keyed.each_value(&:clear!)
|
191
218
|
|
@@ -200,6 +227,7 @@ module Async
|
|
200
227
|
return dirty
|
201
228
|
end
|
202
229
|
|
230
|
+
# Mark the container's keyed instance which ensures that it won't be discarded.
|
203
231
|
def mark?(key)
|
204
232
|
if key
|
205
233
|
if value = @keyed[key]
|
@@ -212,6 +240,7 @@ module Async
|
|
212
240
|
return false
|
213
241
|
end
|
214
242
|
|
243
|
+
# Whether a child instance exists for the given key.
|
215
244
|
def key?(key)
|
216
245
|
if key
|
217
246
|
@keyed.key?(key)
|
@@ -21,7 +21,6 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
require 'fiber'
|
24
|
-
|
25
24
|
require 'async/clock'
|
26
25
|
|
27
26
|
require_relative 'error'
|
@@ -30,6 +29,7 @@ module Async
|
|
30
29
|
module Container
|
31
30
|
# Manages a group of running processes.
|
32
31
|
class Group
|
32
|
+
# Initialize an empty group.
|
33
33
|
def initialize
|
34
34
|
@running = {}
|
35
35
|
|
@@ -40,19 +40,25 @@ module Async
|
|
40
40
|
# @attribute [Hash<IO, Fiber>] the running tasks, indexed by IO.
|
41
41
|
attr :running
|
42
42
|
|
43
|
+
# Whether the group contains any running processes.
|
44
|
+
# @returns [Boolean]
|
43
45
|
def running?
|
44
46
|
@running.any?
|
45
47
|
end
|
46
48
|
|
49
|
+
# Whether the group contains any running processes.
|
50
|
+
# @returns [Boolean]
|
47
51
|
def any?
|
48
52
|
@running.any?
|
49
53
|
end
|
50
54
|
|
55
|
+
# Whether the group is empty.
|
56
|
+
# @returns [Boolean]
|
51
57
|
def empty?
|
52
58
|
@running.empty?
|
53
59
|
end
|
54
60
|
|
55
|
-
#
|
61
|
+
# Sleep for at most the specified duration until some state change occurs.
|
56
62
|
def sleep(duration)
|
57
63
|
self.resume
|
58
64
|
self.suspend
|
@@ -60,6 +66,7 @@ module Async
|
|
60
66
|
self.wait_for_children(duration)
|
61
67
|
end
|
62
68
|
|
69
|
+
# Begin any outstanding queued processes and wait for them indefinitely.
|
63
70
|
def wait
|
64
71
|
self.resume
|
65
72
|
|
@@ -68,6 +75,8 @@ module Async
|
|
68
75
|
end
|
69
76
|
end
|
70
77
|
|
78
|
+
# Interrupt all running processes.
|
79
|
+
# This resumes the controlling fiber with an instance of {Interrupt}.
|
71
80
|
def interrupt
|
72
81
|
Async.logger.debug(self, "Sending interrupt to #{@running.size} running processes...")
|
73
82
|
@running.each_value do |fiber|
|
@@ -75,6 +84,8 @@ module Async
|
|
75
84
|
end
|
76
85
|
end
|
77
86
|
|
87
|
+
# Terminate all running processes.
|
88
|
+
# This resumes the controlling fiber with an instance of {Terminate}.
|
78
89
|
def terminate
|
79
90
|
Async.logger.debug(self, "Sending terminate to #{@running.size} running processes...")
|
80
91
|
@running.each_value do |fiber|
|
@@ -82,6 +93,8 @@ module Async
|
|
82
93
|
end
|
83
94
|
end
|
84
95
|
|
96
|
+
# Stop all child processes using {#terminate}.
|
97
|
+
# @parameter timeout [Boolean | Numeric | Nil] If specified, invoke a graceful shutdown using {#interrupt} first.
|
85
98
|
def stop(timeout = 1)
|
86
99
|
# Use a default timeout if not specified:
|
87
100
|
timeout = 1 if timeout == true
|
@@ -111,6 +124,7 @@ module Async
|
|
111
124
|
self.wait
|
112
125
|
end
|
113
126
|
|
127
|
+
# Wait for a message in the specified {Channel}.
|
114
128
|
def wait_for(channel)
|
115
129
|
io = channel.in
|
116
130
|
|
@@ -137,6 +151,7 @@ module Async
|
|
137
151
|
|
138
152
|
def wait_for_children(duration = nil)
|
139
153
|
if !@running.empty?
|
154
|
+
# Maybe consider using a proper event loop here:
|
140
155
|
readable, _, _ = ::IO.select(@running.keys, nil, nil, duration)
|
141
156
|
|
142
157
|
readable&.each do |io|
|
@@ -25,7 +25,12 @@ require_relative 'threaded'
|
|
25
25
|
|
26
26
|
module Async
|
27
27
|
module Container
|
28
|
+
# Provides a hybrid multi-process multi-thread container.
|
28
29
|
class Hybrid < Forked
|
30
|
+
# Run multiple instances of the same block in the container.
|
31
|
+
# @parameter count [Integer] The number of instances to start.
|
32
|
+
# @parameter forks [Integer] The number of processes to fork.
|
33
|
+
# @parameter threads [Integer] the number of threads to start.
|
29
34
|
def run(count: nil, forks: nil, threads: nil, **options, &block)
|
30
35
|
processor_count = Container.processor_count
|
31
36
|
count ||= processor_count ** 2
|
@@ -22,6 +22,8 @@
|
|
22
22
|
|
23
23
|
module Async
|
24
24
|
module Container
|
25
|
+
# Tracks a key/value pair such that unmarked keys can be identified and cleaned up.
|
26
|
+
# This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container.
|
25
27
|
class Keyed
|
26
28
|
def initialize(key, value)
|
27
29
|
@key = key
|
@@ -29,21 +31,31 @@ module Async
|
|
29
31
|
@marked = true
|
30
32
|
end
|
31
33
|
|
34
|
+
# The key. Normally a symbol or a file-system path.
|
35
|
+
# @attribute [Object]
|
32
36
|
attr :key
|
37
|
+
|
38
|
+
# The value. Normally a child instance of some sort.
|
39
|
+
# @attribute [Object]
|
33
40
|
attr :value
|
34
41
|
|
42
|
+
# Has the instance been marked?
|
43
|
+
# @returns [Boolean]
|
35
44
|
def marked?
|
36
45
|
@marked
|
37
46
|
end
|
38
47
|
|
48
|
+
# Mark the instance. This will indiciate that the value is still in use/active.
|
39
49
|
def mark!
|
40
50
|
@marked = true
|
41
51
|
end
|
42
52
|
|
53
|
+
# Clear the instance. This is normally done before reloading a container.
|
43
54
|
def clear!
|
44
55
|
@marked = false
|
45
56
|
end
|
46
57
|
|
58
|
+
# Stop the instance if it was not marked.
|
47
59
|
def stop?
|
48
60
|
unless @marked
|
49
61
|
@value.stop
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
4
|
#
|
6
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -28,12 +27,12 @@ require_relative 'notify/console'
|
|
28
27
|
module Async
|
29
28
|
module Container
|
30
29
|
module Notify
|
31
|
-
|
32
|
-
@@client = nil
|
30
|
+
@client = nil
|
33
31
|
|
32
|
+
# Select the best available notification client.
|
33
|
+
# We cache the client on a per-process basis. Because that's the relevant scope for process readiness protocols.
|
34
34
|
def self.open!
|
35
|
-
|
36
|
-
@@client ||= (
|
35
|
+
@client ||= (
|
37
36
|
Pipe.open! ||
|
38
37
|
Socket.open! ||
|
39
38
|
Console.open!
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
4
|
#
|
6
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -23,12 +22,17 @@
|
|
23
22
|
|
24
23
|
module Async
|
25
24
|
module Container
|
25
|
+
# Handles the details of several process readiness protocols.
|
26
26
|
module Notify
|
27
27
|
class Client
|
28
|
+
# Notify the parent controller that the child has become ready, with a brief status message.
|
29
|
+
# @parameters message [Hash] Additional details to send with the message.
|
28
30
|
def ready!(**message)
|
29
31
|
send(ready: true, **message)
|
30
32
|
end
|
31
33
|
|
34
|
+
# Notify the parent controller that the child is reloading.
|
35
|
+
# @parameters message [Hash] Additional details to send with the message.
|
32
36
|
def reloading!(**message)
|
33
37
|
message[:ready] = false
|
34
38
|
message[:reloading] = true
|
@@ -37,6 +41,8 @@ module Async
|
|
37
41
|
send(**message)
|
38
42
|
end
|
39
43
|
|
44
|
+
# Notify the parent controller that the child is restarting.
|
45
|
+
# @parameters message [Hash] Additional details to send with the message.
|
40
46
|
def restarting!(**message)
|
41
47
|
message[:ready] = false
|
42
48
|
message[:reloading] = true
|
@@ -45,14 +51,23 @@ module Async
|
|
45
51
|
send(**message)
|
46
52
|
end
|
47
53
|
|
54
|
+
# Notify the parent controller that the child is stopping.
|
55
|
+
# @parameters message [Hash] Additional details to send with the message.
|
48
56
|
def stopping!(**message)
|
49
57
|
message[:stopping] = true
|
58
|
+
|
59
|
+
send(**message)
|
50
60
|
end
|
51
61
|
|
62
|
+
# Notify the parent controller of a status change.
|
63
|
+
# @parameters text [String] The details of the status change.
|
52
64
|
def status!(text)
|
53
65
|
send(status: text)
|
54
66
|
end
|
55
67
|
|
68
|
+
# Notify the parent controller of an error condition.
|
69
|
+
# @parameters text [String] The details of the error condition.
|
70
|
+
# @parameters message [Hash] Additional details to send with the message.
|
56
71
|
def error!(text, **message)
|
57
72
|
send(status: text, **message)
|
58
73
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
4
|
#
|
6
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -28,39 +27,27 @@ require 'console/logger'
|
|
28
27
|
module Async
|
29
28
|
module Container
|
30
29
|
module Notify
|
30
|
+
# Implements a general process readiness protocol with output to the local console.
|
31
31
|
class Console < Client
|
32
|
+
# Open a notification client attached to the current console.
|
32
33
|
def self.open!(logger = ::Console.logger)
|
33
34
|
self.new(logger)
|
34
35
|
end
|
35
36
|
|
37
|
+
# Initialize the notification client.
|
38
|
+
# @parameter logger [Console::Logger] The console logger instance to send messages to.
|
36
39
|
def initialize(logger)
|
37
40
|
@logger = logger
|
38
41
|
end
|
39
42
|
|
43
|
+
# Send a message to the console.
|
40
44
|
def send(level: :debug, **message)
|
41
45
|
@logger.send(level, self) {message}
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def restarting!(**message)
|
49
|
-
message[:ready] = false
|
50
|
-
message[:reloading] = true
|
51
|
-
message[:status] ||= "Restarting..."
|
52
|
-
|
53
|
-
send(**message)
|
54
|
-
end
|
55
|
-
|
56
|
-
def reloading!(**message)
|
57
|
-
message[:ready] = false
|
58
|
-
message[:reloading] = true
|
59
|
-
message[:status] ||= "Reloading..."
|
60
|
-
|
61
|
-
send(**message)
|
62
|
-
end
|
63
|
-
|
48
|
+
# Send an error message to the console.
|
49
|
+
# @parameters text [String] The details of the error condition.
|
50
|
+
# @parameters message [Hash] Additional details to send with the message.
|
64
51
|
def error!(text, **message)
|
65
52
|
send(status: text, level: :error, **message)
|
66
53
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
4
|
#
|
6
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -28,9 +27,12 @@ require 'json'
|
|
28
27
|
module Async
|
29
28
|
module Container
|
30
29
|
module Notify
|
30
|
+
# Implements a process readiness protocol using an inherited pipe file descriptor.
|
31
31
|
class Pipe < Client
|
32
|
+
# The environment variable key which contains the pipe file descriptor.
|
32
33
|
NOTIFY_PIPE = 'NOTIFY_PIPE'
|
33
34
|
|
35
|
+
# Open a notification client attached to the current {NOTIFY_PIPE} if possible.
|
34
36
|
def self.open!(environment = ENV)
|
35
37
|
if descriptor = environment.delete(NOTIFY_PIPE)
|
36
38
|
self.new(::IO.for_fd(descriptor.to_i))
|
@@ -41,6 +43,8 @@ module Async
|
|
41
43
|
return nil
|
42
44
|
end
|
43
45
|
|
46
|
+
# Initialize the notification client.
|
47
|
+
# @parameter io [IO] An IO instance used for sending messages.
|
44
48
|
def initialize(io)
|
45
49
|
@io = io
|
46
50
|
end
|
@@ -72,6 +76,8 @@ module Async
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
79
|
+
# Formats the message using JSON and sends it to the parent controller.
|
80
|
+
# This is suitable for use with {Channel}.
|
75
81
|
def send(**message)
|
76
82
|
data = ::JSON.dump(message)
|
77
83
|
|
@@ -79,10 +85,6 @@ module Async
|
|
79
85
|
@io.flush
|
80
86
|
end
|
81
87
|
|
82
|
-
def ready!(**message)
|
83
|
-
send(ready: true, **message)
|
84
|
-
end
|
85
|
-
|
86
88
|
private
|
87
89
|
|
88
90
|
def environment_for(arguments)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
4
|
#
|
6
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -30,21 +29,31 @@ require 'kernel/sync'
|
|
30
29
|
module Async
|
31
30
|
module Container
|
32
31
|
module Notify
|
32
|
+
# Implements the systemd NOTIFY_SOCKET process readiness protocol.
|
33
|
+
# See <https://www.freedesktop.org/software/systemd/man/sd_notify.html> for more details of the underlying protocol.
|
33
34
|
class Socket < Client
|
35
|
+
# The name of the environment variable which contains the path to the notification socket.
|
34
36
|
NOTIFY_SOCKET = 'NOTIFY_SOCKET'
|
37
|
+
|
38
|
+
# The maximum allowed size of the UDP message.
|
35
39
|
MAXIMUM_MESSAGE_SIZE = 4096
|
36
40
|
|
41
|
+
# Open a notification client attached to the current {NOTIFY_SOCKET} if possible.
|
37
42
|
def self.open!(environment = ENV)
|
38
43
|
if path = environment.delete(NOTIFY_SOCKET)
|
39
44
|
self.new(path)
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
48
|
+
# Initialize the notification client.
|
49
|
+
# @parameter path [String] The path to the UNIX socket used for sending messages to the process manager.
|
43
50
|
def initialize(path)
|
44
51
|
@path = path
|
45
52
|
@endpoint = IO::Endpoint.unix(path, ::Socket::SOCK_DGRAM)
|
46
53
|
end
|
47
54
|
|
55
|
+
# Dump a message in the format requied by `sd_notify`.
|
56
|
+
# @parameter message [Hash] Keys and values should be string convertible objects. Values which are `true`/`false` are converted to `1`/`0` respectively.
|
48
57
|
def dump(message)
|
49
58
|
buffer = String.new
|
50
59
|
|
@@ -62,6 +71,8 @@ module Async
|
|
62
71
|
return buffer
|
63
72
|
end
|
64
73
|
|
74
|
+
# Send the given message.
|
75
|
+
# @parameter message [Hash]
|
65
76
|
def send(**message)
|
66
77
|
data = dump(message)
|
67
78
|
|
@@ -76,6 +87,8 @@ module Async
|
|
76
87
|
end
|
77
88
|
end
|
78
89
|
|
90
|
+
# Send the specified error.
|
91
|
+
# `sd_notify` requires an `errno` key, which defaults to `-1` to indicate a generic error.
|
79
92
|
def error!(text, **message)
|
80
93
|
message[:errno] ||= -1
|
81
94
|
|
@@ -27,8 +27,12 @@ require_relative 'notify/pipe'
|
|
27
27
|
|
28
28
|
module Async
|
29
29
|
module Container
|
30
|
+
# Represents a running child process from the point of view of the parent container.
|
30
31
|
class Process < Channel
|
32
|
+
# Represents a running child process from the point of view of the child process.
|
31
33
|
class Instance < Notify::Pipe
|
34
|
+
# Wrap an instance around the {Process} instance from within the forked child.
|
35
|
+
# @parameter process [Process] The process intance to wrap.
|
32
36
|
def self.for(process)
|
33
37
|
instance = self.new(process.out)
|
34
38
|
|
@@ -46,16 +50,22 @@ module Async
|
|
46
50
|
@name = nil
|
47
51
|
end
|
48
52
|
|
53
|
+
# Set the process title to the specified value.
|
54
|
+
# @parameter value [String] The name of the process.
|
49
55
|
def name= value
|
50
56
|
if @name = value
|
51
57
|
::Process.setproctitle(@name)
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
61
|
+
# The name of the process.
|
62
|
+
# @returns [String]
|
55
63
|
def name
|
56
64
|
@name
|
57
65
|
end
|
58
66
|
|
67
|
+
# Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}.
|
68
|
+
# This method replaces the child process with the new executable, thus this method never returns.
|
59
69
|
def exec(*arguments, ready: true, **options)
|
60
70
|
if ready
|
61
71
|
self.ready!(status: "(exec)") if ready
|
@@ -63,10 +73,13 @@ module Async
|
|
63
73
|
self.before_spawn(arguments, options)
|
64
74
|
end
|
65
75
|
|
76
|
+
# TODO prefer **options... but it doesn't support redirections on < 2.7
|
66
77
|
::Process.exec(*arguments, options)
|
67
78
|
end
|
68
79
|
end
|
69
80
|
|
81
|
+
# Fork a child process appropriate for a container.
|
82
|
+
# @returns [Process]
|
70
83
|
def self.fork(**options)
|
71
84
|
self.new(**options) do |process|
|
72
85
|
::Process.fork do
|
@@ -96,6 +109,8 @@ module Async
|
|
96
109
|
# end
|
97
110
|
# end
|
98
111
|
|
112
|
+
# Initialize the process.
|
113
|
+
# @parameter name [String] The name to use for the child process.
|
99
114
|
def initialize(name: nil)
|
100
115
|
super()
|
101
116
|
|
@@ -109,6 +124,8 @@ module Async
|
|
109
124
|
self.close_write
|
110
125
|
end
|
111
126
|
|
127
|
+
# Set the name of the process.
|
128
|
+
# Invokes {::Process.setproctitle} if invoked in the child process.
|
112
129
|
def name= value
|
113
130
|
@name = value
|
114
131
|
|
@@ -116,12 +133,17 @@ module Async
|
|
116
133
|
::Process.setproctitle(@name) if @pid.nil?
|
117
134
|
end
|
118
135
|
|
136
|
+
# The name of the process.
|
137
|
+
# @attribute [String]
|
119
138
|
attr :name
|
120
139
|
|
140
|
+
# A human readable representation of the process.
|
141
|
+
# @returns [String]
|
121
142
|
def to_s
|
122
143
|
"\#<#{self.class} #{@name}>"
|
123
144
|
end
|
124
145
|
|
146
|
+
# Invoke {#terminate!} and then {#wait} for the child process to exit.
|
125
147
|
def close
|
126
148
|
self.terminate!
|
127
149
|
self.wait
|
@@ -129,18 +151,22 @@ module Async
|
|
129
151
|
super
|
130
152
|
end
|
131
153
|
|
154
|
+
# Send `SIGINT` to the child process.
|
132
155
|
def interrupt!
|
133
156
|
unless @status
|
134
157
|
::Process.kill(:INT, @pid)
|
135
158
|
end
|
136
159
|
end
|
137
160
|
|
161
|
+
# Send `SIGTERM` to the child process.
|
138
162
|
def terminate!
|
139
163
|
unless @status
|
140
164
|
::Process.kill(:TERM, @pid)
|
141
165
|
end
|
142
166
|
end
|
143
167
|
|
168
|
+
# Wait for the child process to exit.
|
169
|
+
# @returns [::Process::Status] The process exit status.
|
144
170
|
def wait
|
145
171
|
if @pid && @status.nil?
|
146
172
|
_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
|
@@ -24,6 +24,7 @@ require 'async/reactor'
|
|
24
24
|
|
25
25
|
module Async
|
26
26
|
module Container
|
27
|
+
# Tracks various statistics relating to child instances in a container.
|
27
28
|
class Statistics
|
28
29
|
def initialize
|
29
30
|
@spawns = 0
|
@@ -31,26 +32,41 @@ module Async
|
|
31
32
|
@failures = 0
|
32
33
|
end
|
33
34
|
|
35
|
+
# How many child instances have been spawned.
|
36
|
+
# @attribute [Integer]
|
34
37
|
attr :spawns
|
38
|
+
|
39
|
+
# How many child instances have been restarted.
|
40
|
+
# @attribute [Integer]
|
35
41
|
attr :restarts
|
42
|
+
|
43
|
+
# How many child instances have failed.
|
44
|
+
# @attribute [Integer]
|
36
45
|
attr :failures
|
37
46
|
|
47
|
+
# Increment the number of spawns by 1.
|
38
48
|
def spawn!
|
39
49
|
@spawns += 1
|
40
50
|
end
|
41
51
|
|
52
|
+
# Increment the number of restarts by 1.
|
42
53
|
def restart!
|
43
54
|
@restarts += 1
|
44
55
|
end
|
45
56
|
|
57
|
+
# Increment the number of failures by 1.
|
46
58
|
def failure!
|
47
59
|
@failures += 1
|
48
60
|
end
|
49
61
|
|
62
|
+
# Whether there have been any failures.
|
63
|
+
# @returns [Boolean] If the failure count is greater than 0.
|
50
64
|
def failed?
|
51
65
|
@failures > 0
|
52
66
|
end
|
53
67
|
|
68
|
+
# Append another statistics instance into this one.
|
69
|
+
# @parameter other [Statistics] The statistics to append.
|
54
70
|
def << other
|
55
71
|
@spawns += other.spawns
|
56
72
|
@restarts += other.restarts
|
@@ -28,14 +28,22 @@ require 'async/logger'
|
|
28
28
|
|
29
29
|
module Async
|
30
30
|
module Container
|
31
|
+
# Represents a running child thread from the point of view of the parent container.
|
31
32
|
class Thread < Channel
|
33
|
+
# Used to propagate the exit status of a child process invoked by {Instance#exec}.
|
32
34
|
class Exit < Exception
|
35
|
+
# Initialize the exit status.
|
36
|
+
# @parameter status [::Process::Status] The process exit status.
|
33
37
|
def initialize(status)
|
34
38
|
@status = status
|
35
39
|
end
|
36
40
|
|
41
|
+
# The process exit status.
|
42
|
+
# @attribute [::Process::Status]
|
37
43
|
attr :status
|
38
44
|
|
45
|
+
# The process exit status if it was an error.
|
46
|
+
# @returns [::Process::Status | Nil]
|
39
47
|
def error
|
40
48
|
unless status.success?
|
41
49
|
status
|
@@ -43,7 +51,10 @@ module Async
|
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
54
|
+
# Represents a running child thread from the point of view of the child thread.
|
46
55
|
class Instance < Notify::Pipe
|
56
|
+
# Wrap an instance around the {Thread} instance from within the threaded child.
|
57
|
+
# @parameter thread [Thread] The thread intance to wrap.
|
47
58
|
def self.for(thread)
|
48
59
|
instance = self.new(thread.out)
|
49
60
|
|
@@ -57,14 +68,20 @@ module Async
|
|
57
68
|
super
|
58
69
|
end
|
59
70
|
|
71
|
+
# Set the name of the thread.
|
72
|
+
# @parameter value [String] The name to set.
|
60
73
|
def name= value
|
61
74
|
@thread.name = value
|
62
75
|
end
|
63
76
|
|
77
|
+
# Get the name of the thread.
|
78
|
+
# @returns [String]
|
64
79
|
def name
|
65
80
|
@thread.name
|
66
81
|
end
|
67
82
|
|
83
|
+
# Execute a child process using {::Process.spawn}. In order to simulate {::Process.exec}, an {Exit} instance is raised to propagage exit status.
|
84
|
+
# This creates the illusion that this method does not return (normally).
|
68
85
|
def exec(*arguments, ready: true, **options)
|
69
86
|
if ready
|
70
87
|
self.ready!(status: "(spawn)") if ready
|
@@ -91,6 +108,8 @@ module Async
|
|
91
108
|
end
|
92
109
|
end
|
93
110
|
|
111
|
+
# Initialize the thread.
|
112
|
+
# @parameter name [String] The name to use for the child thread.
|
94
113
|
def initialize(name: nil)
|
95
114
|
super()
|
96
115
|
|
@@ -116,18 +135,25 @@ module Async
|
|
116
135
|
end
|
117
136
|
end
|
118
137
|
|
138
|
+
# Set the name of the thread.
|
139
|
+
# @parameter value [String] The name to set.
|
119
140
|
def name= value
|
120
141
|
@thread.name = value
|
121
142
|
end
|
122
143
|
|
144
|
+
# Get the name of the thread.
|
145
|
+
# @returns [String]
|
123
146
|
def name
|
124
147
|
@thread.name
|
125
148
|
end
|
126
149
|
|
150
|
+
# A human readable representation of the thread.
|
151
|
+
# @returns [String]
|
127
152
|
def to_s
|
128
153
|
"\#<#{self.class} #{@thread.name}>"
|
129
154
|
end
|
130
155
|
|
156
|
+
# Invoke {#terminate!} and then {#wait} for the child thread to exit.
|
131
157
|
def close
|
132
158
|
self.terminate!
|
133
159
|
self.wait
|
@@ -135,14 +161,18 @@ module Async
|
|
135
161
|
super
|
136
162
|
end
|
137
163
|
|
164
|
+
# Raise {Interrupt} in the child thread.
|
138
165
|
def interrupt!
|
139
166
|
@thread.raise(Interrupt)
|
140
167
|
end
|
141
168
|
|
169
|
+
# Raise {Terminate} in the child thread.
|
142
170
|
def terminate!
|
143
171
|
@thread.raise(Terminate)
|
144
172
|
end
|
145
173
|
|
174
|
+
# Wait for the thread to exit and return he exit status.
|
175
|
+
# @returns [Status]
|
146
176
|
def wait
|
147
177
|
if @waiter
|
148
178
|
@waiter.join
|
@@ -152,15 +182,21 @@ module Async
|
|
152
182
|
return @status
|
153
183
|
end
|
154
184
|
|
185
|
+
# A pseudo exit-status wrapper.
|
155
186
|
class Status
|
156
|
-
|
157
|
-
|
187
|
+
# Initialise the status.
|
188
|
+
# @parameter error [::Process::Status] The exit status of the child thread.
|
189
|
+
def initialize(error = nil)
|
190
|
+
@error = error
|
158
191
|
end
|
159
192
|
|
193
|
+
# Whether the status represents a successful outcome.
|
194
|
+
# @returns [Boolean]
|
160
195
|
def success?
|
161
|
-
@
|
196
|
+
@error.nil?
|
162
197
|
end
|
163
198
|
|
199
|
+
# A human readable representation of the status.
|
164
200
|
def to_s
|
165
201
|
"\#<#{self.class} #{success? ? "success" : "failure"}>"
|
166
202
|
end
|
@@ -168,6 +204,7 @@ module Async
|
|
168
204
|
|
169
205
|
protected
|
170
206
|
|
207
|
+
# Invoked by the @waiter thread to indicate the outcome of the child thread.
|
171
208
|
def finished(error = nil)
|
172
209
|
if error
|
173
210
|
Async.logger.error(self) {error}
|
@@ -24,13 +24,17 @@ require_relative 'generic'
|
|
24
24
|
require_relative 'thread'
|
25
25
|
|
26
26
|
module Async
|
27
|
-
# Manages a reactor within one or more threads.
|
28
27
|
module Container
|
28
|
+
# A multi-thread container which uses {Thread.fork}.
|
29
29
|
class Threaded < Generic
|
30
|
+
# Indicates that this is not a multi-process container.
|
30
31
|
def self.multiprocess?
|
31
32
|
false
|
32
33
|
end
|
33
34
|
|
35
|
+
# Start a named child thread and execute the provided block in it.
|
36
|
+
# @parameter name [String] The name (title) of the child process.
|
37
|
+
# @parameter block [Proc] The block to execute in the child process.
|
34
38
|
def start(name, &block)
|
35
39
|
Thread.fork(name: name, &block)
|
36
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-container
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -161,7 +161,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
161
161
|
requirements:
|
162
162
|
- - "~>"
|
163
163
|
- !ruby/object:Gem::Version
|
164
|
-
version: '2.
|
164
|
+
version: '2.5'
|
165
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
166
|
requirements:
|
167
167
|
- - ">="
|