async 1.31.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/barrier.md +36 -0
- data/lib/async/barrier.rb +14 -6
- data/lib/async/clock.rb +11 -0
- data/lib/async/condition.md +31 -0
- data/lib/async/condition.rb +27 -16
- data/lib/async/node.rb +27 -12
- data/lib/async/notification.rb +4 -4
- data/lib/async/queue.rb +8 -21
- data/lib/async/reactor.rb +11 -316
- data/lib/async/scheduler.rb +235 -48
- data/lib/async/semaphore.md +41 -0
- data/lib/async/semaphore.rb +8 -24
- data/lib/async/task.rb +68 -70
- data/lib/async/version.rb +1 -1
- data/lib/async/wrapper.rb +19 -179
- data/lib/async.rb +0 -5
- data/lib/kernel/async.rb +26 -2
- data/lib/kernel/sync.rb +14 -4
- metadata +10 -10
- data/lib/async/debug/monitor.rb +0 -47
- data/lib/async/debug/selector.rb +0 -82
- data/lib/async/logger.rb +0 -28
@@ -0,0 +1,41 @@
|
|
1
|
+
A synchronization primitive, which limits access to a given resource, such as a limited number of database connections, open files, or network connections.
|
2
|
+
|
3
|
+
## Example
|
4
|
+
|
5
|
+
~~~ ruby
|
6
|
+
require 'async'
|
7
|
+
require 'async/semaphore'
|
8
|
+
require 'net/http'
|
9
|
+
|
10
|
+
Sync do
|
11
|
+
# Only allow two concurrent tasks at a time:
|
12
|
+
semaphore = Async::Semaphore.new(2)
|
13
|
+
|
14
|
+
# Generate an array of 10 numbers:
|
15
|
+
terms = ['ruby', 'python', 'go', 'java', 'c++']
|
16
|
+
|
17
|
+
# Search for the terms:
|
18
|
+
terms.each do |term|
|
19
|
+
semaphore.async do |task|
|
20
|
+
Console.logger.info("Searching for #{term}...")
|
21
|
+
response = Net::HTTP.get(URI "https://www.google.com/search?q=#{term}")
|
22
|
+
Console.logger.info("Got response #{response.size} bytes.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
~~~
|
27
|
+
|
28
|
+
### Output
|
29
|
+
|
30
|
+
~~~
|
31
|
+
0.0s info: Searching for ruby... [ec=0x3c] [pid=50523]
|
32
|
+
0.04s info: Searching for python... [ec=0x21c] [pid=50523]
|
33
|
+
1.7s info: Got response 182435 bytes. [ec=0x3c] [pid=50523]
|
34
|
+
1.71s info: Searching for go... [ec=0x834] [pid=50523]
|
35
|
+
3.0s info: Got response 204854 bytes. [ec=0x21c] [pid=50523]
|
36
|
+
3.0s info: Searching for java... [ec=0xf64] [pid=50523]
|
37
|
+
4.32s info: Got response 103235 bytes. [ec=0x834] [pid=50523]
|
38
|
+
4.32s info: Searching for c++... [ec=0x12d4] [pid=50523]
|
39
|
+
4.65s info: Got response 109697 bytes. [ec=0xf64] [pid=50523]
|
40
|
+
6.64s info: Got response 87249 bytes. [ec=0x12d4] [pid=50523]
|
41
|
+
~~~
|
data/lib/async/semaphore.rb
CHANGED
@@ -21,8 +21,11 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
module Async
|
24
|
-
# A
|
24
|
+
# A synchronization primitive, which limits access to a given resource.
|
25
|
+
# @public Since `stable-v1`.
|
25
26
|
class Semaphore
|
27
|
+
# @parameter limit [Integer] The maximum number of times the semaphore can be acquired before it blocks.
|
28
|
+
# @parameter parent [Task | Semaphore | Nil] The parent for holding any children tasks.
|
26
29
|
def initialize(limit = 1, parent: nil)
|
27
30
|
@count = 0
|
28
31
|
@limit = limit
|
@@ -40,25 +43,6 @@ module Async
|
|
40
43
|
# The tasks waiting on this semaphore.
|
41
44
|
attr :waiting
|
42
45
|
|
43
|
-
# Allow setting the limit. This is useful for cases where the semaphore is used to limit the number of concurrent tasks, but the number of tasks is not known in advance or needs to be modified.
|
44
|
-
#
|
45
|
-
# On increasing the limit, some tasks may be immediately resumed. On decreasing the limit, some tasks may execute until the count is < than the limit.
|
46
|
-
#
|
47
|
-
# @parameter limit [Integer] The new limit.
|
48
|
-
def limit= limit
|
49
|
-
difference = limit - @limit
|
50
|
-
@limit = limit
|
51
|
-
|
52
|
-
# We can't suspend
|
53
|
-
if difference > 0
|
54
|
-
difference.times do
|
55
|
-
break unless fiber = @waiting.shift
|
56
|
-
|
57
|
-
fiber.resume
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
46
|
# Is the semaphore currently acquired?
|
63
47
|
def empty?
|
64
48
|
@count.zero?
|
@@ -86,8 +70,8 @@ module Async
|
|
86
70
|
|
87
71
|
# Acquire the semaphore, block if we are at the limit.
|
88
72
|
# If no block is provided, you must call release manually.
|
89
|
-
# @
|
90
|
-
# @
|
73
|
+
# @yields {...} When the semaphore can be acquired.
|
74
|
+
# @returns The result of the block if invoked.
|
91
75
|
def acquire
|
92
76
|
wait
|
93
77
|
|
@@ -108,7 +92,7 @@ module Async
|
|
108
92
|
|
109
93
|
while (@limit - @count) > 0 and fiber = @waiting.shift
|
110
94
|
if fiber.alive?
|
111
|
-
|
95
|
+
Fiber.scheduler.resume(fiber)
|
112
96
|
end
|
113
97
|
end
|
114
98
|
end
|
@@ -121,7 +105,7 @@ module Async
|
|
121
105
|
|
122
106
|
if blocking?
|
123
107
|
@waiting << fiber
|
124
|
-
|
108
|
+
Fiber.scheduler.transfer while blocking?
|
125
109
|
end
|
126
110
|
rescue Exception
|
127
111
|
@waiting.delete(fiber)
|
data/lib/async/task.rb
CHANGED
@@ -21,7 +21,6 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
require 'fiber'
|
24
|
-
require 'forwardable'
|
25
24
|
|
26
25
|
require_relative 'node'
|
27
26
|
require_relative 'condition'
|
@@ -38,55 +37,45 @@ module Async
|
|
38
37
|
true
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
40
|
+
def transfer
|
42
41
|
@task.stop
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
|
-
#
|
48
|
-
#
|
46
|
+
# Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
|
47
|
+
# @public Since `stable-v1`.
|
48
|
+
class TimeoutError < StandardError
|
49
|
+
def initialize(message = "execution expired")
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Encapsulates the state of a running task and it's result.
|
55
|
+
# @public Since `stable-v1`.
|
49
56
|
class Task < Node
|
50
|
-
|
51
|
-
|
52
|
-
# Yield the unerlying `result` for the task. If the result
|
53
|
-
# is an Exception, then that result will be raised an its
|
54
|
-
# exception.
|
55
|
-
# @return [Object] result of the task
|
56
|
-
# @raise [Exception] if the result is an exception
|
57
|
-
# @yield [result] result of the task if a block if given.
|
57
|
+
# @deprecated With no replacement.
|
58
58
|
def self.yield
|
59
|
-
|
60
|
-
result = yield
|
61
|
-
else
|
62
|
-
result = Fiber.yield
|
63
|
-
end
|
64
|
-
|
65
|
-
if result.is_a? Exception
|
66
|
-
raise result
|
67
|
-
else
|
68
|
-
return result
|
69
|
-
end
|
59
|
+
Fiber.scheduler.transfer
|
70
60
|
end
|
71
61
|
|
72
62
|
# Create a new task.
|
73
|
-
# @
|
74
|
-
# @
|
75
|
-
def initialize(
|
76
|
-
super(parent
|
77
|
-
|
78
|
-
@reactor = reactor
|
63
|
+
# @parameter reactor [Reactor] the reactor this task will run within.
|
64
|
+
# @parameter parent [Task] the parent task.
|
65
|
+
def initialize(parent = Task.current?, finished: nil, **options, &block)
|
66
|
+
super(parent, **options)
|
79
67
|
|
80
68
|
@status = :initialized
|
81
69
|
@result = nil
|
82
70
|
@finished = finished
|
83
71
|
|
84
|
-
@
|
85
|
-
|
86
|
-
@fiber = make_fiber(&block)
|
72
|
+
@block = block
|
73
|
+
@fiber = nil
|
87
74
|
end
|
88
75
|
|
89
|
-
|
76
|
+
def reactor
|
77
|
+
self.root
|
78
|
+
end
|
90
79
|
|
91
80
|
if Fiber.current.respond_to?(:backtrace)
|
92
81
|
def backtrace(*arguments)
|
@@ -98,14 +87,19 @@ module Async
|
|
98
87
|
"\#<#{self.description} (#{@status})>"
|
99
88
|
end
|
100
89
|
|
101
|
-
# @
|
102
|
-
|
90
|
+
# @deprecated Prefer {Kernel#sleep} except when compatibility with `stable-v1` is required.
|
91
|
+
def sleep(duration = nil)
|
92
|
+
super
|
93
|
+
end
|
103
94
|
|
104
|
-
|
95
|
+
# @deprecated Replaced by {Scheduler#timeout_after}.
|
96
|
+
def with_timeout(timeout, exception = TimeoutError, message = "execution expired", &block)
|
97
|
+
Fiber.scheduler.timeout_after(timeout, exception, message, &block)
|
98
|
+
end
|
105
99
|
|
106
100
|
# Yield back to the reactor and allow other fibers to execute.
|
107
101
|
def yield
|
108
|
-
|
102
|
+
Fiber.scheduler.yield
|
109
103
|
end
|
110
104
|
|
111
105
|
# @attr fiber [Fiber] The fiber which is being used for the execution of this task.
|
@@ -123,14 +117,14 @@ module Async
|
|
123
117
|
if @status == :initialized
|
124
118
|
@status = :running
|
125
119
|
|
126
|
-
|
120
|
+
schedule(arguments)
|
127
121
|
else
|
128
122
|
raise RuntimeError, "Task already running!"
|
129
123
|
end
|
130
124
|
end
|
131
125
|
|
132
126
|
def async(*arguments, **options, &block)
|
133
|
-
task = Task.new(
|
127
|
+
task = Task.new(self, **options, &block)
|
134
128
|
|
135
129
|
task.run(*arguments)
|
136
130
|
|
@@ -138,22 +132,26 @@ module Async
|
|
138
132
|
end
|
139
133
|
|
140
134
|
# Retrieve the current result of the task. Will cause the caller to wait until result is available.
|
141
|
-
# @
|
142
|
-
# @
|
135
|
+
# @raises[RuntimeError] If the task's fiber is the current fiber.
|
136
|
+
# @returns [Object] The final expression/result of the task's block.
|
143
137
|
def wait
|
144
|
-
raise
|
138
|
+
raise "Cannot wait on own fiber" if Fiber.current.equal?(@fiber)
|
145
139
|
|
146
140
|
if running?
|
147
141
|
@finished ||= Condition.new
|
148
142
|
@finished.wait
|
143
|
+
end
|
144
|
+
|
145
|
+
case @result
|
146
|
+
when Exception
|
147
|
+
raise @result
|
149
148
|
else
|
150
|
-
|
149
|
+
return @result
|
151
150
|
end
|
152
151
|
end
|
153
152
|
|
154
|
-
#
|
155
|
-
|
156
|
-
# Soon to become attr :result
|
153
|
+
# Access the result of the task without waiting. May be nil if the task is not completed.
|
154
|
+
attr :result
|
157
155
|
|
158
156
|
# Stop the task and all of its children.
|
159
157
|
def stop(later = false)
|
@@ -165,15 +163,15 @@ module Async
|
|
165
163
|
if self.running?
|
166
164
|
if self.current?
|
167
165
|
if later
|
168
|
-
|
166
|
+
Fiber.scheduler.push Stop::Later.new(self)
|
169
167
|
else
|
170
168
|
raise Stop, "Stopping current task!"
|
171
169
|
end
|
172
170
|
elsif @fiber&.alive?
|
173
171
|
begin
|
174
|
-
@fiber
|
172
|
+
Fiber.scheduler.raise(@fiber, Stop)
|
175
173
|
rescue FiberError
|
176
|
-
|
174
|
+
Fiber.scheduler.push Stop::Later.new(self)
|
177
175
|
end
|
178
176
|
end
|
179
177
|
else
|
@@ -183,14 +181,14 @@ module Async
|
|
183
181
|
end
|
184
182
|
|
185
183
|
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
|
186
|
-
# @
|
187
|
-
# @
|
184
|
+
# @returns [Task]
|
185
|
+
# @raises[RuntimeError] If task was not {set!} for the current fiber.
|
188
186
|
def self.current
|
189
187
|
Thread.current[:async_task] or raise RuntimeError, "No async task available!"
|
190
188
|
end
|
191
189
|
|
192
190
|
# Check if there is a task defined for the current fiber.
|
193
|
-
# @
|
191
|
+
# @returns [Task | Nil]
|
194
192
|
def self.current?
|
195
193
|
Thread.current[:async_task]
|
196
194
|
end
|
@@ -200,13 +198,13 @@ module Async
|
|
200
198
|
end
|
201
199
|
|
202
200
|
# Check if the task is running.
|
203
|
-
# @
|
201
|
+
# @returns [Boolean]
|
204
202
|
def running?
|
205
203
|
@status == :running
|
206
204
|
end
|
207
205
|
|
208
206
|
# Whether we can remove this node from the reactor graph.
|
209
|
-
# @
|
207
|
+
# @returns [Boolean]
|
210
208
|
def finished?
|
211
209
|
super && @status != :running
|
212
210
|
end
|
@@ -230,35 +228,34 @@ module Async
|
|
230
228
|
private
|
231
229
|
|
232
230
|
# This is a very tricky aspect of tasks to get right. I've modelled it after `Thread` but it's slightly different in that the exception can propagate back up through the reactor. If the user writes code which raises an exception, that exception should always be visible, i.e. cause a failure. If it's not visible, such code fails silently and can be very difficult to debug.
|
233
|
-
|
231
|
+
# As an explcit choice, the user can start a task which doesn't propagate exceptions. This only applies to `StandardError` and derived tasks. This allows tasks to internally capture their error state which is raised when invoking `Task#result` similar to how `Thread#join` works. This mode makes {ruby Async::Task} behave more like a promise, and you would need to ensure that someone calls `Task#result` otherwise you might miss important errors.
|
232
|
+
def fail!(exception = nil, propagate = true)
|
234
233
|
@status = :failed
|
235
234
|
@result = exception
|
236
235
|
|
237
|
-
if
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
Console.logger.debug(self, exception)
|
245
|
-
end
|
236
|
+
if propagate
|
237
|
+
raise
|
238
|
+
elsif @finished.nil?
|
239
|
+
# If no one has called wait, we log this as an error:
|
240
|
+
Console.logger.error(self) {$!}
|
241
|
+
else
|
242
|
+
Console.logger.debug(self) {$!}
|
246
243
|
end
|
247
244
|
end
|
248
245
|
|
249
246
|
def stop!
|
250
|
-
# logger.
|
247
|
+
# Console.logger.info(self, self.annotation) {"Task was stopped with #{@children&.size.inspect} children!"}
|
251
248
|
@status = :stopped
|
252
249
|
|
253
250
|
stop_children(true)
|
254
251
|
end
|
255
252
|
|
256
|
-
def
|
257
|
-
Fiber.new do
|
253
|
+
def schedule(arguments)
|
254
|
+
@fiber = Fiber.new do
|
258
255
|
set!
|
259
256
|
|
260
257
|
begin
|
261
|
-
@result =
|
258
|
+
@result = @block.call(self, *arguments)
|
262
259
|
@status = :complete
|
263
260
|
# Console.logger.debug(self) {"Task was completed with #{@children.size} children!"}
|
264
261
|
rescue Stop
|
@@ -268,10 +265,12 @@ module Async
|
|
268
265
|
rescue Exception => exception
|
269
266
|
fail!(exception, true)
|
270
267
|
ensure
|
271
|
-
# Console.logger.
|
268
|
+
# Console.logger.info(self) {"Task ensure $! = #{$!} with #{@children&.size.inspect} children!"}
|
272
269
|
finish!
|
273
270
|
end
|
274
271
|
end
|
272
|
+
|
273
|
+
self.root.resume(@fiber)
|
275
274
|
end
|
276
275
|
|
277
276
|
# Finish the current task, and all bound bound IO objects.
|
@@ -292,7 +291,6 @@ module Async
|
|
292
291
|
def set!
|
293
292
|
# This is actually fiber-local:
|
294
293
|
Thread.current[:async_task] = self
|
295
|
-
Console.logger = @logger if @logger
|
296
294
|
end
|
297
295
|
end
|
298
296
|
end
|
data/lib/async/version.rb
CHANGED
data/lib/async/wrapper.rb
CHANGED
@@ -20,220 +20,60 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
require 'nio'
|
24
|
-
|
25
23
|
module Async
|
26
24
|
# Represents an asynchronous IO within a reactor.
|
25
|
+
# @deprecated With no replacement. Prefer native interfaces.
|
27
26
|
class Wrapper
|
27
|
+
# An exception that occurs when the asynchronous operation was cancelled.
|
28
28
|
class Cancelled < StandardError
|
29
|
-
class From
|
30
|
-
def initialize
|
31
|
-
@backtrace = caller[5..-1]
|
32
|
-
end
|
33
|
-
|
34
|
-
attr :backtrace
|
35
|
-
|
36
|
-
def cause
|
37
|
-
nil
|
38
|
-
end
|
39
|
-
|
40
|
-
def message
|
41
|
-
"Cancelled"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize
|
46
|
-
super "The operation has been cancelled!"
|
47
|
-
|
48
|
-
@cause = From.new
|
49
|
-
end
|
50
|
-
|
51
|
-
attr :cause
|
52
29
|
end
|
53
30
|
|
54
|
-
#
|
55
|
-
|
56
|
-
def initialize
|
57
|
-
super "A fiber is already waiting!"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# @param io the native object to wrap.
|
62
|
-
# @param reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
|
31
|
+
# @parameter io the native object to wrap.
|
32
|
+
# @parameter reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
|
63
33
|
def initialize(io, reactor = nil)
|
64
34
|
@io = io
|
65
|
-
|
66
35
|
@reactor = reactor
|
67
|
-
@monitor = nil
|
68
36
|
|
69
|
-
@
|
70
|
-
@writable = nil
|
71
|
-
@any = nil
|
37
|
+
@timeout = nil
|
72
38
|
end
|
73
39
|
|
74
|
-
|
75
|
-
self.class.new(@io.dup, @reactor)
|
76
|
-
end
|
40
|
+
attr_accessor :reactor
|
77
41
|
|
78
|
-
def
|
79
|
-
|
80
|
-
return unless @monitor
|
81
|
-
|
82
|
-
readiness = @monitor.readiness
|
83
|
-
|
84
|
-
if @readable and (readiness == :r or readiness == :rw)
|
85
|
-
@readable.resume(*arguments)
|
86
|
-
end
|
87
|
-
|
88
|
-
if @writable and (readiness == :w or readiness == :rw)
|
89
|
-
@writable.resume(*arguments)
|
90
|
-
end
|
91
|
-
|
92
|
-
if @any
|
93
|
-
@any.resume(*arguments)
|
94
|
-
end
|
42
|
+
def dup
|
43
|
+
self.class.new(@io.dup)
|
95
44
|
end
|
96
45
|
|
97
46
|
# The underlying native `io`.
|
98
47
|
attr :io
|
99
48
|
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
# The monitor for this wrapper, if any.
|
104
|
-
attr :monitor
|
105
|
-
|
106
|
-
# Bind this wrapper to a different reactor. Assign nil to convert to an unbound wrapper (can be used from any reactor/task but with slightly increased overhead.)
|
107
|
-
# Binding to a reactor is purely a performance consideration. Generally, I don't like APIs that exist only due to optimisations. This is borderline, so consider this functionality semi-private.
|
108
|
-
def reactor= reactor
|
109
|
-
return if @reactor.equal?(reactor)
|
110
|
-
|
111
|
-
cancel_monitor
|
112
|
-
|
113
|
-
@reactor = reactor
|
49
|
+
# Wait for the io to become readable.
|
50
|
+
def wait_readable(timeout = @timeout)
|
51
|
+
@io.to_io.wait_readable(timeout) or raise TimeoutError
|
114
52
|
end
|
115
53
|
|
116
|
-
# Wait for the io to become
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
self.reactor = Task.current.reactor
|
121
|
-
|
122
|
-
begin
|
123
|
-
@readable = Fiber.current
|
124
|
-
wait_for(timeout)
|
125
|
-
ensure
|
126
|
-
@readable = nil
|
127
|
-
@monitor.interests = interests if @monitor
|
128
|
-
end
|
54
|
+
# Wait for the io to become writable.
|
55
|
+
def wait_priority(timeout = @timeout)
|
56
|
+
@io.to_io.wait_priority(timeout) or raise TimeoutError
|
129
57
|
end
|
130
58
|
|
131
59
|
# Wait for the io to become writable.
|
132
|
-
def wait_writable(timeout =
|
133
|
-
|
134
|
-
|
135
|
-
self.reactor = Task.current.reactor
|
136
|
-
|
137
|
-
begin
|
138
|
-
@writable = Fiber.current
|
139
|
-
wait_for(timeout)
|
140
|
-
ensure
|
141
|
-
@writable = nil
|
142
|
-
@monitor.interests = interests if @monitor
|
143
|
-
end
|
60
|
+
def wait_writable(timeout = @timeout)
|
61
|
+
@io.to_io.wait_writable(timeout) or raise TimeoutError
|
144
62
|
end
|
145
63
|
|
146
64
|
# Wait fo the io to become either readable or writable.
|
147
|
-
# @
|
148
|
-
def wait_any(timeout =
|
149
|
-
|
150
|
-
|
151
|
-
self.reactor = Task.current.reactor
|
152
|
-
|
153
|
-
begin
|
154
|
-
@any = Fiber.current
|
155
|
-
wait_for(timeout)
|
156
|
-
ensure
|
157
|
-
@any = nil
|
158
|
-
@monitor.interests = interests if @monitor
|
159
|
-
end
|
65
|
+
# @parameter duration [Float] timeout after the given duration if not `nil`.
|
66
|
+
def wait_any(timeout = @timeout)
|
67
|
+
@io.wait_any(timeout) or raise TimeoutError
|
160
68
|
end
|
161
69
|
|
162
70
|
# Close the io and monitor.
|
163
71
|
def close
|
164
|
-
cancel_monitor
|
165
|
-
|
166
72
|
@io.close
|
167
73
|
end
|
168
74
|
|
169
75
|
def closed?
|
170
76
|
@io.closed?
|
171
77
|
end
|
172
|
-
|
173
|
-
private
|
174
|
-
|
175
|
-
# What an abomination.
|
176
|
-
def interests
|
177
|
-
if @any
|
178
|
-
return :rw
|
179
|
-
elsif @readable
|
180
|
-
if @writable
|
181
|
-
return :rw
|
182
|
-
else
|
183
|
-
return :r
|
184
|
-
end
|
185
|
-
elsif @writable
|
186
|
-
return :w
|
187
|
-
end
|
188
|
-
|
189
|
-
return nil
|
190
|
-
end
|
191
|
-
|
192
|
-
def cancel_monitor
|
193
|
-
if @readable
|
194
|
-
readable = @readable
|
195
|
-
@readable = nil
|
196
|
-
|
197
|
-
readable.resume(Cancelled.new)
|
198
|
-
end
|
199
|
-
|
200
|
-
if @writable
|
201
|
-
writable = @writable
|
202
|
-
@writable = nil
|
203
|
-
|
204
|
-
writable.resume(Cancelled.new)
|
205
|
-
end
|
206
|
-
|
207
|
-
if @any
|
208
|
-
any = @any
|
209
|
-
@any = nil
|
210
|
-
|
211
|
-
any.resume(Cancelled.new)
|
212
|
-
end
|
213
|
-
|
214
|
-
if @monitor
|
215
|
-
@monitor.close
|
216
|
-
@monitor = nil
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def wait_for(timeout)
|
221
|
-
if @monitor
|
222
|
-
@monitor.interests = interests
|
223
|
-
else
|
224
|
-
@monitor = @reactor.register(@io, interests, self)
|
225
|
-
end
|
226
|
-
|
227
|
-
# If the user requested an explicit timeout for this operation:
|
228
|
-
if timeout
|
229
|
-
@reactor.with_timeout(timeout) do
|
230
|
-
Task.yield
|
231
|
-
end
|
232
|
-
else
|
233
|
-
Task.yield
|
234
|
-
end
|
235
|
-
|
236
|
-
return true
|
237
|
-
end
|
238
78
|
end
|
239
79
|
end
|
data/lib/async.rb
CHANGED
@@ -21,15 +21,10 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
require_relative "async/version"
|
24
|
-
require_relative "async/logger"
|
25
24
|
require_relative "async/reactor"
|
26
25
|
|
27
26
|
require_relative "kernel/async"
|
28
27
|
require_relative "kernel/sync"
|
29
28
|
|
30
29
|
module Async
|
31
|
-
# Invoke `Reactor.run` with all arguments/block.
|
32
|
-
def self.run(*arguments, &block)
|
33
|
-
Reactor.run(*arguments, &block)
|
34
|
-
end
|
35
30
|
end
|
data/lib/kernel/async.rb
CHANGED
@@ -24,7 +24,31 @@ require_relative "../async/reactor"
|
|
24
24
|
|
25
25
|
module Kernel
|
26
26
|
# Run the given block of code in a task, asynchronously, creating a reactor if necessary.
|
27
|
-
|
28
|
-
|
27
|
+
#
|
28
|
+
# The preferred method to invoke asynchronous behavior at the top level.
|
29
|
+
#
|
30
|
+
# - When invoked within an existing reactor task, it will run the given block
|
31
|
+
# asynchronously. Will return the task once it has been scheduled.
|
32
|
+
# - When invoked at the top level, will create and run a reactor, and invoke
|
33
|
+
# the block as an asynchronous task. Will block until the reactor finishes
|
34
|
+
# running.
|
35
|
+
#
|
36
|
+
# @yields {|task| ...} The block that will execute asynchronously.
|
37
|
+
# @parameter task [Async::Task] The task that is executing the given block.
|
38
|
+
#
|
39
|
+
# @public Since `stable-v1`.
|
40
|
+
# @asynchronous May block until given block completes executing.
|
41
|
+
def Async(...)
|
42
|
+
if current = ::Async::Task.current?
|
43
|
+
return current.async(...)
|
44
|
+
else
|
45
|
+
reactor = ::Async::Reactor.new
|
46
|
+
|
47
|
+
begin
|
48
|
+
return reactor.run(...)
|
49
|
+
ensure
|
50
|
+
Fiber.set_scheduler(nil)
|
51
|
+
end
|
52
|
+
end
|
29
53
|
end
|
30
54
|
end
|