async 1.32.1 → 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 -320
- 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 -114
- 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,57 +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)
|
87
|
-
|
88
|
-
@defer_stop = nil
|
72
|
+
@block = block
|
73
|
+
@fiber = nil
|
89
74
|
end
|
90
75
|
|
91
|
-
|
76
|
+
def reactor
|
77
|
+
self.root
|
78
|
+
end
|
92
79
|
|
93
80
|
if Fiber.current.respond_to?(:backtrace)
|
94
81
|
def backtrace(*arguments)
|
@@ -100,14 +87,19 @@ module Async
|
|
100
87
|
"\#<#{self.description} (#{@status})>"
|
101
88
|
end
|
102
89
|
|
103
|
-
# @
|
104
|
-
|
90
|
+
# @deprecated Prefer {Kernel#sleep} except when compatibility with `stable-v1` is required.
|
91
|
+
def sleep(duration = nil)
|
92
|
+
super
|
93
|
+
end
|
105
94
|
|
106
|
-
|
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
|
107
99
|
|
108
100
|
# Yield back to the reactor and allow other fibers to execute.
|
109
101
|
def yield
|
110
|
-
|
102
|
+
Fiber.scheduler.yield
|
111
103
|
end
|
112
104
|
|
113
105
|
# @attr fiber [Fiber] The fiber which is being used for the execution of this task.
|
@@ -125,14 +117,14 @@ module Async
|
|
125
117
|
if @status == :initialized
|
126
118
|
@status = :running
|
127
119
|
|
128
|
-
|
120
|
+
schedule(arguments)
|
129
121
|
else
|
130
122
|
raise RuntimeError, "Task already running!"
|
131
123
|
end
|
132
124
|
end
|
133
125
|
|
134
126
|
def async(*arguments, **options, &block)
|
135
|
-
task = Task.new(
|
127
|
+
task = Task.new(self, **options, &block)
|
136
128
|
|
137
129
|
task.run(*arguments)
|
138
130
|
|
@@ -140,22 +132,26 @@ module Async
|
|
140
132
|
end
|
141
133
|
|
142
134
|
# Retrieve the current result of the task. Will cause the caller to wait until result is available.
|
143
|
-
# @
|
144
|
-
# @
|
135
|
+
# @raises[RuntimeError] If the task's fiber is the current fiber.
|
136
|
+
# @returns [Object] The final expression/result of the task's block.
|
145
137
|
def wait
|
146
|
-
raise
|
138
|
+
raise "Cannot wait on own fiber" if Fiber.current.equal?(@fiber)
|
147
139
|
|
148
140
|
if running?
|
149
141
|
@finished ||= Condition.new
|
150
142
|
@finished.wait
|
143
|
+
end
|
144
|
+
|
145
|
+
case @result
|
146
|
+
when Exception
|
147
|
+
raise @result
|
151
148
|
else
|
152
|
-
|
149
|
+
return @result
|
153
150
|
end
|
154
151
|
end
|
155
152
|
|
156
|
-
#
|
157
|
-
|
158
|
-
# 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
|
159
155
|
|
160
156
|
# Stop the task and all of its children.
|
161
157
|
def stop(later = false)
|
@@ -164,25 +160,18 @@ module Async
|
|
164
160
|
return
|
165
161
|
end
|
166
162
|
|
167
|
-
# If we are deferring stop...
|
168
|
-
if @defer_stop == false
|
169
|
-
# Don't stop now... but update the state so we know we need to stop later.
|
170
|
-
@defer_stop = true
|
171
|
-
return false
|
172
|
-
end
|
173
|
-
|
174
163
|
if self.running?
|
175
164
|
if self.current?
|
176
165
|
if later
|
177
|
-
|
166
|
+
Fiber.scheduler.push Stop::Later.new(self)
|
178
167
|
else
|
179
168
|
raise Stop, "Stopping current task!"
|
180
169
|
end
|
181
170
|
elsif @fiber&.alive?
|
182
171
|
begin
|
183
|
-
@fiber
|
172
|
+
Fiber.scheduler.raise(@fiber, Stop)
|
184
173
|
rescue FiberError
|
185
|
-
|
174
|
+
Fiber.scheduler.push Stop::Later.new(self)
|
186
175
|
end
|
187
176
|
end
|
188
177
|
else
|
@@ -191,50 +180,15 @@ module Async
|
|
191
180
|
end
|
192
181
|
end
|
193
182
|
|
194
|
-
# Defer the handling of stop. During the execution of the given block, if a stop is requested, it will be deferred until the block exits. This is useful for ensuring graceful shutdown of servers and other long-running tasks. You should wrap the response handling code in a defer_stop block to ensure that the task is stopped when the response is complete but not before.
|
195
|
-
#
|
196
|
-
# You can nest calls to defer_stop, but the stop will only be deferred until the outermost block exits.
|
197
|
-
#
|
198
|
-
# If stop is invoked a second time, it will be immediately executed.
|
199
|
-
#
|
200
|
-
# @yields {} The block of code to execute.
|
201
|
-
def defer_stop
|
202
|
-
# Tri-state variable for controlling stop:
|
203
|
-
# - nil: defer_stop has not been called.
|
204
|
-
# - false: defer_stop has been called and we are not stopping.
|
205
|
-
# - true: defer_stop has been called and we will stop when exiting the block.
|
206
|
-
if @defer_stop.nil?
|
207
|
-
# If we are not deferring stop already, we can defer it now:
|
208
|
-
@defer_stop = false
|
209
|
-
|
210
|
-
begin
|
211
|
-
yield
|
212
|
-
rescue Stop
|
213
|
-
# If we are exiting due to a stop, we shouldn't try to invoke stop again:
|
214
|
-
@defer_stop = nil
|
215
|
-
raise
|
216
|
-
ensure
|
217
|
-
# If we were asked to stop, we should do so now:
|
218
|
-
if @defer_stop
|
219
|
-
@defer_stop = nil
|
220
|
-
self.stop
|
221
|
-
end
|
222
|
-
end
|
223
|
-
else
|
224
|
-
# If we are deferring stop already, entering it again is a no-op.
|
225
|
-
yield
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
183
|
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
|
230
|
-
# @
|
231
|
-
# @
|
184
|
+
# @returns [Task]
|
185
|
+
# @raises[RuntimeError] If task was not {set!} for the current fiber.
|
232
186
|
def self.current
|
233
187
|
Thread.current[:async_task] or raise RuntimeError, "No async task available!"
|
234
188
|
end
|
235
189
|
|
236
190
|
# Check if there is a task defined for the current fiber.
|
237
|
-
# @
|
191
|
+
# @returns [Task | Nil]
|
238
192
|
def self.current?
|
239
193
|
Thread.current[:async_task]
|
240
194
|
end
|
@@ -244,13 +198,13 @@ module Async
|
|
244
198
|
end
|
245
199
|
|
246
200
|
# Check if the task is running.
|
247
|
-
# @
|
201
|
+
# @returns [Boolean]
|
248
202
|
def running?
|
249
203
|
@status == :running
|
250
204
|
end
|
251
205
|
|
252
206
|
# Whether we can remove this node from the reactor graph.
|
253
|
-
# @
|
207
|
+
# @returns [Boolean]
|
254
208
|
def finished?
|
255
209
|
super && @status != :running
|
256
210
|
end
|
@@ -274,35 +228,34 @@ module Async
|
|
274
228
|
private
|
275
229
|
|
276
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.
|
277
|
-
|
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)
|
278
233
|
@status = :failed
|
279
234
|
@result = exception
|
280
235
|
|
281
|
-
if
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
Console.logger.debug(self, exception)
|
289
|
-
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) {$!}
|
290
243
|
end
|
291
244
|
end
|
292
245
|
|
293
246
|
def stop!
|
294
|
-
# logger.
|
247
|
+
# Console.logger.info(self, self.annotation) {"Task was stopped with #{@children&.size.inspect} children!"}
|
295
248
|
@status = :stopped
|
296
249
|
|
297
250
|
stop_children(true)
|
298
251
|
end
|
299
252
|
|
300
|
-
def
|
301
|
-
Fiber.new do
|
253
|
+
def schedule(arguments)
|
254
|
+
@fiber = Fiber.new do
|
302
255
|
set!
|
303
256
|
|
304
257
|
begin
|
305
|
-
@result =
|
258
|
+
@result = @block.call(self, *arguments)
|
306
259
|
@status = :complete
|
307
260
|
# Console.logger.debug(self) {"Task was completed with #{@children.size} children!"}
|
308
261
|
rescue Stop
|
@@ -312,10 +265,12 @@ module Async
|
|
312
265
|
rescue Exception => exception
|
313
266
|
fail!(exception, true)
|
314
267
|
ensure
|
315
|
-
# Console.logger.
|
268
|
+
# Console.logger.info(self) {"Task ensure $! = #{$!} with #{@children&.size.inspect} children!"}
|
316
269
|
finish!
|
317
270
|
end
|
318
271
|
end
|
272
|
+
|
273
|
+
self.root.resume(@fiber)
|
319
274
|
end
|
320
275
|
|
321
276
|
# Finish the current task, and all bound bound IO objects.
|
@@ -336,7 +291,6 @@ module Async
|
|
336
291
|
def set!
|
337
292
|
# This is actually fiber-local:
|
338
293
|
Thread.current[:async_task] = self
|
339
|
-
Console.logger = @logger if @logger
|
340
294
|
end
|
341
295
|
end
|
342
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
|