async-container 0.16.6 → 0.16.11
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 +46 -25
- data/lib/async/container/error.rb +21 -0
- data/lib/async/container/forked.rb +5 -1
- data/lib/async/container/generic.rb +58 -17
- data/lib/async/container/group.rb +19 -4
- 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 +8 -6
- data/lib/async/container/notify/server.rb +0 -1
- data/lib/async/container/notify/socket.rb +14 -1
- data/lib/async/container/process.rb +28 -2
- data/lib/async/container/statistics.rb +16 -0
- data/lib/async/container/thread.rb +41 -6
- data/lib/async/container/threaded.rb +5 -1
- data/lib/async/container/version.rb +1 -1
- metadata +5 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b6ba3c06851500a3d47b2fc72147caf9dc34fa0fd0cd759c7c88f36df808987
|
4
|
+
data.tar.gz: cd43c233e26d54801270ec66b797f84dc7309c53f3b8aef3af5a75bcf484c5c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a726816975444a5eb5b92569970814e2bc997814466d86c08a4053adf8304a6f13036955dff3718f80d5fe43ef9195491be3045e54f1bdb247838199759062b8
|
7
|
+
data.tar.gz: d0dd11f404e8ddb9c5dcb05d996651dbbde1a61ed404ef241df1c81ee5e28ecfd09c80504b6ddf8465c425dcccddca65d6f52d1c4e87dda9141b21b4b366b27f
|
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,49 +63,68 @@ 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!
|
110
124
|
|
111
|
-
|
125
|
+
Console.logger.debug(self) {"Restarting container..."}
|
112
126
|
else
|
113
|
-
|
127
|
+
Console.logger.debug(self) {"Starting container..."}
|
114
128
|
end
|
115
129
|
|
116
130
|
container = self.create_container
|
@@ -120,27 +134,27 @@ 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.
|
127
|
-
|
141
|
+
Console.logger.debug(self, "Waiting for startup...")
|
128
142
|
container.wait_until_ready
|
129
|
-
|
143
|
+
Console.logger.debug(self, "Finished startup.")
|
130
144
|
|
131
145
|
if container.failed?
|
132
146
|
@notify&.error!($!.to_s)
|
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:
|
140
154
|
old_container = @container
|
141
155
|
@container = container
|
142
156
|
|
143
|
-
|
157
|
+
Console.logger.debug(self, "Stopping old container...")
|
144
158
|
old_container&.stop
|
145
159
|
@notify&.ready!
|
146
160
|
rescue
|
@@ -150,31 +164,33 @@ 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
|
|
156
|
-
|
171
|
+
Console.logger.info(self) {"Reloading container: #{@container}..."}
|
157
172
|
|
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.
|
165
|
-
|
180
|
+
Console.logger.debug(self, "Waiting for startup...")
|
166
181
|
@container.wait_until_ready
|
167
|
-
|
182
|
+
Console.logger.debug(self, "Finished startup.")
|
168
183
|
|
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
|
@@ -185,6 +201,10 @@ module Async
|
|
185
201
|
raise Terminate
|
186
202
|
end
|
187
203
|
|
204
|
+
hangup_action = Signal.trap(:HUP) do
|
205
|
+
raise Hangup
|
206
|
+
end
|
207
|
+
|
188
208
|
self.start
|
189
209
|
|
190
210
|
while @container&.running?
|
@@ -194,8 +214,8 @@ module Async
|
|
194
214
|
if handler = @signals[exception.signo]
|
195
215
|
begin
|
196
216
|
handler.call
|
197
|
-
rescue
|
198
|
-
|
217
|
+
rescue SetupError => error
|
218
|
+
Console.logger.error(self) {error}
|
199
219
|
end
|
200
220
|
else
|
201
221
|
raise
|
@@ -212,6 +232,7 @@ module Async
|
|
212
232
|
# Restore the interrupt handler:
|
213
233
|
Signal.trap(:INT, interrupt_action)
|
214
234
|
Signal.trap(:TERM, terminate_action)
|
235
|
+
Signal.trap(:HUP, hangup_action)
|
215
236
|
end
|
216
237
|
end
|
217
238
|
end
|
@@ -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,25 @@ module Async
|
|
34
35
|
super(SIGTERM)
|
35
36
|
end
|
36
37
|
end
|
38
|
+
|
39
|
+
class Hangup < SignalException
|
40
|
+
SIGHUP = Signal.list['HUP']
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
super(SIGHUP)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Represents the error which occured when a container failed to start up correctly.
|
48
|
+
class SetupError < Error
|
49
|
+
def initialize(container)
|
50
|
+
super("Could not create container!")
|
51
|
+
|
52
|
+
@container = container
|
53
|
+
end
|
54
|
+
|
55
|
+
# The container that failed.
|
56
|
+
attr :container
|
57
|
+
end
|
37
58
|
end
|
38
59
|
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,14 +106,20 @@ 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
|
+
Console.logger.debug(self) do |buffer|
|
106
123
|
buffer.puts "Waiting for ready:"
|
107
124
|
@state.each do |child, state|
|
108
125
|
buffer.puts "\t#{child.class}: #{state.inspect}"
|
@@ -117,28 +134,34 @@ 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)
|
123
142
|
|
124
143
|
if @group.running?
|
125
|
-
|
144
|
+
Console.logger.warn(self) {"Group is still running after stopping it!"}
|
126
145
|
end
|
127
146
|
ensure
|
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
|
|
134
157
|
if mark?(key)
|
135
|
-
|
158
|
+
Console.logger.debug(self) {"Reusing existing child for #{key}: #{name}"}
|
136
159
|
return false
|
137
160
|
end
|
138
161
|
|
139
162
|
@statistics.spawn!
|
140
163
|
|
141
|
-
|
164
|
+
fiber do
|
142
165
|
while @running
|
143
166
|
child = self.start(name, &block)
|
144
167
|
|
@@ -153,10 +176,10 @@ module Async
|
|
153
176
|
end
|
154
177
|
|
155
178
|
if status.success?
|
156
|
-
|
179
|
+
Console.logger.info(self) {"#{child} exited with #{status}"}
|
157
180
|
else
|
158
181
|
@statistics.failure!
|
159
|
-
|
182
|
+
Console.logger.error(self) {status}
|
160
183
|
end
|
161
184
|
|
162
185
|
if restart
|
@@ -166,18 +189,14 @@ module Async
|
|
166
189
|
end
|
167
190
|
end
|
168
191
|
# ensure
|
169
|
-
#
|
192
|
+
# Console.logger.error(self) {$!} if $!
|
170
193
|
end.resume
|
171
194
|
|
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)
|
@@ -241,6 +270,18 @@ module Async
|
|
241
270
|
|
242
271
|
@state.delete(child)
|
243
272
|
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
if Fiber.respond_to?(:blocking?)
|
277
|
+
def fiber(&block)
|
278
|
+
Fiber.new(blocking: true, &block)
|
279
|
+
end
|
280
|
+
else
|
281
|
+
def fiber(&block)
|
282
|
+
Fiber.new(&block)
|
283
|
+
end
|
284
|
+
end
|
244
285
|
end
|
245
286
|
end
|
246
287
|
end
|