async 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/lib/async/{debug.rb → debug/monitor.rb} +4 -43
- data/lib/async/debug/selector.rb +81 -0
- data/lib/async/reactor.rb +11 -22
- data/lib/async/version.rb +1 -1
- data/lib/async/wrapper.rb +135 -39
- data/spec/async/logger_spec.rb +23 -21
- data/spec/async/reactor_spec.rb +47 -25
- data/spec/async/wrapper_spec.rb +109 -21
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8342091acbc6da153552ca269239949b27898eb7bff55ca9425687e86f314751
|
4
|
+
data.tar.gz: fa8fd142667ba572fd66554d25bf9490625811f91005c9c4b0ac76f9329dba69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8e9d025de96cee15d9967e344a81dd01e3c3f6e64895f1deba1f2fd77edbbff2d9d7d63294cdaca6492add2112e5b0742e326c70845c52b8041b10eba96fd17
|
7
|
+
data.tar.gz: 4f07e3153419bd128c53c86f63fca88dcde579d906689e9bbaff488b6f782f11d3950a1f6754894f20880aa8aa09fcde50b7608c0c47b97ef0fe9314b5d5343b
|
data/Rakefile
CHANGED
@@ -18,11 +18,9 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'nio'
|
22
|
-
|
23
21
|
module Async
|
24
|
-
|
25
|
-
class
|
22
|
+
module Debug
|
23
|
+
class Monitor
|
26
24
|
def initialize(monitor, selector)
|
27
25
|
@monitor = monitor
|
28
26
|
@selector = selector
|
@@ -40,47 +38,10 @@ module Async
|
|
40
38
|
def respond_to?(*args)
|
41
39
|
@monitor.respond_to?(*args)
|
42
40
|
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize(selector)
|
46
|
-
@selector = selector
|
47
|
-
@monitors = {}
|
48
|
-
end
|
49
|
-
|
50
|
-
def register(io, interests)
|
51
|
-
$stderr.puts "Registering #{io.inspect} for #{interests}."
|
52
41
|
|
53
|
-
|
54
|
-
|
42
|
+
def inspect
|
43
|
+
"\#<#{self.class} io=#{@monitor.io.inspect} interests=#{@monitor.interests.inspect} readiness=#{@monitor.readiness.inspect}>"
|
55
44
|
end
|
56
|
-
|
57
|
-
@monitors[io.fileno] = io
|
58
|
-
|
59
|
-
MonitorProxy.new(@selector.register(io, interests), self)
|
60
|
-
end
|
61
|
-
|
62
|
-
def deregister(io)
|
63
|
-
$stderr.puts "Deregistering #{io.inspect}."
|
64
|
-
|
65
|
-
unless @monitors.delete(io.fileno)
|
66
|
-
raise RuntimeError, "Trying to remove monitor for #{io.inspect} but it was not registered!"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def wakeup
|
71
|
-
@selector.wakeup
|
72
|
-
end
|
73
|
-
|
74
|
-
def close
|
75
|
-
if @monitors.any?
|
76
|
-
$stderr.puts "Trying to close selector with active monitors: #{@monitors.values.inspect}!"
|
77
|
-
end
|
78
|
-
|
79
|
-
@selector.close
|
80
|
-
end
|
81
|
-
|
82
|
-
def select(*args)
|
83
|
-
@selector.select(*args)
|
84
45
|
end
|
85
46
|
end
|
86
47
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'monitor'
|
22
|
+
require_relative '../logger'
|
23
|
+
|
24
|
+
require 'nio'
|
25
|
+
|
26
|
+
module Async
|
27
|
+
module Debug
|
28
|
+
class Selector
|
29
|
+
def initialize(selector = NIO::Selector.new)
|
30
|
+
@selector = selector
|
31
|
+
@monitors = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def register(object, interests)
|
35
|
+
Async.logger.debug(self) {"Registering #{object.inspect} for #{interests}."}
|
36
|
+
|
37
|
+
unless io = ::IO.try_convert(object)
|
38
|
+
raise RuntimeError, "Could not convert #{io} into IO!"
|
39
|
+
end
|
40
|
+
|
41
|
+
if monitor = @monitors[io.fileno]
|
42
|
+
raise RuntimeError, "Trying to register monitor for #{object.inspect} but it was already registered: #{monitor.inspect}!"
|
43
|
+
end
|
44
|
+
|
45
|
+
monitor = Monitor.new(@selector.register(object, interests), self)
|
46
|
+
|
47
|
+
@monitors[io.fileno] = monitor
|
48
|
+
|
49
|
+
return monitor
|
50
|
+
end
|
51
|
+
|
52
|
+
def deregister(object)
|
53
|
+
Async.logger.debug(self) {"Deregistering #{object.inspect}."}
|
54
|
+
|
55
|
+
unless io = ::IO.try_convert(object)
|
56
|
+
raise RuntimeError, "Could not convert #{io} into IO!"
|
57
|
+
end
|
58
|
+
|
59
|
+
unless @monitors.delete(io.fileno)
|
60
|
+
raise RuntimeError, "Trying to remove monitor for #{io.inspect} but it was not registered!"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def wakeup
|
65
|
+
@selector.wakeup
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
if @monitors.any?
|
70
|
+
raise RuntimeError, "Trying to close selector with active monitors: #{@monitors.values.inspect}!"
|
71
|
+
end
|
72
|
+
ensure
|
73
|
+
@selector.close
|
74
|
+
end
|
75
|
+
|
76
|
+
def select(*args)
|
77
|
+
@selector.select(*args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/async/reactor.rb
CHANGED
@@ -31,16 +31,6 @@ module Async
|
|
31
31
|
class TimeoutError < RuntimeError
|
32
32
|
end
|
33
33
|
|
34
|
-
class MonitorError < RuntimeError
|
35
|
-
def initialize(monitor)
|
36
|
-
super "Event detected on IO #{monitor.io.inspect} (#{monitor.interests} -> #{monitor.readiness}) without corresponding fiber!"
|
37
|
-
|
38
|
-
@monitor = monitor
|
39
|
-
end
|
40
|
-
|
41
|
-
attr :monitor
|
42
|
-
end
|
43
|
-
|
44
34
|
# An asynchronous, cooperatively scheduled event reactor.
|
45
35
|
class Reactor < Node
|
46
36
|
extend Forwardable
|
@@ -68,10 +58,10 @@ module Async
|
|
68
58
|
end
|
69
59
|
end
|
70
60
|
|
71
|
-
def initialize
|
72
|
-
super
|
61
|
+
def initialize(parent = nil, selector: NIO::Selector.new)
|
62
|
+
super(parent)
|
73
63
|
|
74
|
-
@selector =
|
64
|
+
@selector = selector
|
75
65
|
@timers = Timers::Group.new
|
76
66
|
|
77
67
|
@ready = []
|
@@ -86,6 +76,10 @@ module Async
|
|
86
76
|
# @attr stopped [Boolean]
|
87
77
|
attr :stopped
|
88
78
|
|
79
|
+
def stopped?
|
80
|
+
@stopped
|
81
|
+
end
|
82
|
+
|
89
83
|
def_delegators :@timers, :every, :after
|
90
84
|
|
91
85
|
# Start an asynchronous task within the specified reactor. The task will be
|
@@ -112,10 +106,9 @@ module Async
|
|
112
106
|
return task
|
113
107
|
end
|
114
108
|
|
115
|
-
def register(
|
116
|
-
monitor = @selector.register(
|
117
|
-
|
118
|
-
monitor.value = Fiber.current
|
109
|
+
def register(io, interest, value = Fiber.current)
|
110
|
+
monitor = @selector.register(io, interest)
|
111
|
+
monitor.value = value
|
119
112
|
|
120
113
|
return monitor
|
121
114
|
end
|
@@ -186,11 +179,7 @@ module Async
|
|
186
179
|
# Async.logger.debug(self) {"Selecting with #{@children.count} fibers interval = #{interval.inspect}..."}
|
187
180
|
if monitors = @selector.select(interval)
|
188
181
|
monitors.each do |monitor|
|
189
|
-
|
190
|
-
fiber.resume # if fiber.alive?
|
191
|
-
else
|
192
|
-
raise MonitorError.new(monitor)
|
193
|
-
end
|
182
|
+
monitor.value.resume
|
194
183
|
end
|
195
184
|
end
|
196
185
|
end until @stopped
|
data/lib/async/version.rb
CHANGED
data/lib/async/wrapper.rb
CHANGED
@@ -23,14 +23,46 @@ require 'nio'
|
|
23
23
|
module Async
|
24
24
|
# Represents an asynchronous IO within a reactor.
|
25
25
|
class Wrapper
|
26
|
+
class Cancelled < StandardError
|
27
|
+
def initialize
|
28
|
+
super "The operation has been cancelled!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# wait_readable, wait_writable and wait_any are not re-entrant, and will raise this failure.
|
33
|
+
class WaitError < StandardError
|
34
|
+
def initialize
|
35
|
+
super "A fiber is already waiting!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
26
39
|
# @param io the native object to wrap.
|
27
40
|
# @param reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
|
28
|
-
# @param bound [Boolean] whether the underlying socket will be closed if the wrapper is closed.
|
29
41
|
def initialize(io, reactor = nil)
|
30
42
|
@io = io
|
31
43
|
|
32
44
|
@reactor = reactor
|
33
45
|
@monitor = nil
|
46
|
+
|
47
|
+
@readable = nil
|
48
|
+
@writable = nil
|
49
|
+
@any = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def resume(*args)
|
53
|
+
readiness = @monitor.readiness
|
54
|
+
|
55
|
+
if @readable and (readiness == :r or readiness == :rw)
|
56
|
+
@readable.resume(*args)
|
57
|
+
end
|
58
|
+
|
59
|
+
if @writable and (readiness == :w or readiness == :rw)
|
60
|
+
@writable.resume(*args)
|
61
|
+
end
|
62
|
+
|
63
|
+
if @any
|
64
|
+
@any.resume(*args)
|
65
|
+
end
|
34
66
|
end
|
35
67
|
|
36
68
|
# The underlying native `io`.
|
@@ -39,74 +71,138 @@ module Async
|
|
39
71
|
# The reactor this wrapper is associated with, if any.
|
40
72
|
attr :reactor
|
41
73
|
|
74
|
+
# The monitor for this wrapper, if any.
|
75
|
+
attr :monitor
|
76
|
+
|
42
77
|
# 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.)
|
43
78
|
# 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.
|
44
79
|
def reactor= reactor
|
45
|
-
if @
|
46
|
-
|
47
|
-
|
48
|
-
end
|
80
|
+
return if @reactor.equal?(reactor)
|
81
|
+
|
82
|
+
cancel_monitor
|
49
83
|
|
50
84
|
@reactor = reactor
|
51
85
|
end
|
52
86
|
|
53
87
|
# Wait for the io to become readable.
|
54
88
|
def wait_readable(duration = nil)
|
55
|
-
|
89
|
+
raise WaitError if @readable
|
90
|
+
|
91
|
+
self.reactor = Task.current.reactor
|
92
|
+
|
93
|
+
begin
|
94
|
+
@readable = Fiber.current
|
95
|
+
wait_for(duration)
|
96
|
+
ensure
|
97
|
+
@readable = nil
|
98
|
+
@monitor.interests = interests if @monitor
|
99
|
+
end
|
56
100
|
end
|
57
101
|
|
58
102
|
# Wait for the io to become writable.
|
59
103
|
def wait_writable(duration = nil)
|
60
|
-
|
104
|
+
raise WaitError if @writable
|
105
|
+
|
106
|
+
self.reactor = Task.current.reactor
|
107
|
+
|
108
|
+
begin
|
109
|
+
@writable = Fiber.current
|
110
|
+
wait_for(duration)
|
111
|
+
ensure
|
112
|
+
@writable = nil
|
113
|
+
@monitor.interests = interests if @monitor
|
114
|
+
end
|
61
115
|
end
|
62
116
|
|
63
117
|
# Wait fo the io to become either readable or writable.
|
64
|
-
# @param interests [:r | :w | :rw] what events to wait for.
|
65
118
|
# @param duration [Float] timeout after the given duration if not `nil`.
|
66
|
-
def wait_any(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
wait_for(@reactor, @monitor, duration)
|
78
|
-
ensure
|
79
|
-
@monitor.interests = nil
|
80
|
-
end
|
81
|
-
else
|
82
|
-
reactor = Task.current.reactor
|
83
|
-
monitor = reactor.register(@io, interests)
|
84
|
-
|
85
|
-
begin
|
86
|
-
wait_for(reactor, monitor, duration)
|
87
|
-
ensure
|
88
|
-
monitor.close
|
89
|
-
end
|
119
|
+
def wait_any(duration = nil)
|
120
|
+
raise WaitError if @any
|
121
|
+
|
122
|
+
self.reactor = Task.current.reactor
|
123
|
+
|
124
|
+
begin
|
125
|
+
@any = Fiber.current
|
126
|
+
wait_for(duration)
|
127
|
+
ensure
|
128
|
+
@any = nil
|
129
|
+
@monitor.interests = interests if @monitor
|
90
130
|
end
|
91
131
|
end
|
92
132
|
|
93
133
|
# Close the io and monitor.
|
94
134
|
def close
|
95
|
-
|
96
|
-
@monitor.close
|
97
|
-
@monitor = nil
|
98
|
-
end
|
135
|
+
cancel_monitor
|
99
136
|
|
100
137
|
@io.close
|
101
138
|
end
|
102
139
|
|
140
|
+
def closed?
|
141
|
+
@io.closed?
|
142
|
+
end
|
143
|
+
|
103
144
|
private
|
104
145
|
|
105
|
-
|
146
|
+
# What an abomination.
|
147
|
+
def interests
|
148
|
+
if @any
|
149
|
+
return :rw
|
150
|
+
elsif @readable
|
151
|
+
if @writable
|
152
|
+
return :rw
|
153
|
+
else
|
154
|
+
return :r
|
155
|
+
end
|
156
|
+
elsif @writable
|
157
|
+
return :w
|
158
|
+
end
|
159
|
+
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def cancel_monitor
|
164
|
+
if @readable
|
165
|
+
readable = @readable
|
166
|
+
@readable = nil
|
167
|
+
|
168
|
+
readable.resume(Cancelled.new)
|
169
|
+
end
|
170
|
+
|
171
|
+
if @writable
|
172
|
+
writable = @writable
|
173
|
+
@writable = nil
|
174
|
+
|
175
|
+
writable.resume(Cancelled.new)
|
176
|
+
end
|
177
|
+
|
178
|
+
if @any
|
179
|
+
any = @any
|
180
|
+
@any = nil
|
181
|
+
|
182
|
+
any.resume(Cancelled.new)
|
183
|
+
end
|
184
|
+
|
185
|
+
if @monitor
|
186
|
+
@monitor.close
|
187
|
+
@monitor = nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def wait_for(duration)
|
192
|
+
if @monitor
|
193
|
+
@monitor.interests = interests
|
194
|
+
else
|
195
|
+
@monitor = @reactor.register(@io, interests, self)
|
196
|
+
end
|
197
|
+
|
106
198
|
# If the user requested an explicit timeout for this operation:
|
107
199
|
if duration
|
108
|
-
reactor.timeout(duration) do
|
109
|
-
|
200
|
+
@reactor.timeout(duration) do
|
201
|
+
begin
|
202
|
+
Task.yield
|
203
|
+
rescue Async::TimeoutError
|
204
|
+
return false
|
205
|
+
end
|
110
206
|
end
|
111
207
|
else
|
112
208
|
Task.yield
|
data/spec/async/logger_spec.rb
CHANGED
@@ -19,29 +19,31 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
RSpec.describe Async.logger do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
let!(:verbose) {$VERBOSE}
|
26
|
-
after {$VERBOSE = verbose}
|
27
|
-
|
28
|
-
it 'should set default log level' do
|
29
|
-
$DEBUG = false
|
30
|
-
$VERBOSE = false
|
22
|
+
describe '::default_log_level' do
|
23
|
+
let!(:debug) {$DEBUG}
|
24
|
+
after {$DEBUG = debug}
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
it 'should set default log level based on $DEBUG' do
|
36
|
-
$DEBUG = true
|
26
|
+
let!(:verbose) {$VERBOSE}
|
27
|
+
after {$VERBOSE = verbose}
|
37
28
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
29
|
+
it 'should set default log level' do
|
30
|
+
$DEBUG = false
|
31
|
+
$VERBOSE = false
|
32
|
+
|
33
|
+
expect(Async.default_log_level).to be == Logger::WARN
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should set default log level based on $DEBUG' do
|
37
|
+
$DEBUG = true
|
38
|
+
|
39
|
+
expect(Async.default_log_level).to be == Logger::DEBUG
|
40
|
+
end
|
44
41
|
|
45
|
-
|
42
|
+
it 'should set default log level based on $VERBOSE' do
|
43
|
+
$DEBUG = false
|
44
|
+
$VERBOSE = true
|
45
|
+
|
46
|
+
expect(Async.default_log_level).to be == Logger::INFO
|
47
|
+
end
|
46
48
|
end
|
47
49
|
end
|
data/spec/async/reactor_spec.rb
CHANGED
@@ -19,38 +19,60 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
RSpec.describe Async::Reactor do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
task
|
28
|
-
|
22
|
+
describe '#run' do
|
23
|
+
it "can run tasks on different fibers" do
|
24
|
+
outer_fiber = Fiber.current
|
25
|
+
inner_fiber = nil
|
26
|
+
|
27
|
+
described_class.run do |task|
|
28
|
+
task.sleep(0)
|
29
|
+
inner_fiber = Fiber.current
|
30
|
+
end
|
31
|
+
|
32
|
+
expect(inner_fiber).to_not be nil
|
33
|
+
expect(outer_fiber).to_not be == inner_fiber
|
29
34
|
end
|
30
|
-
|
31
|
-
expect(inner_fiber).to_not be nil
|
32
|
-
expect(outer_fiber).to_not be == inner_fiber
|
33
35
|
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
describe '#stop' do
|
38
|
+
it "can be stop reactor" do
|
39
|
+
state = nil
|
40
|
+
|
41
|
+
subject.async do |task|
|
42
|
+
state = :started
|
43
|
+
task.sleep(10)
|
44
|
+
state = :stopped
|
45
|
+
end
|
46
|
+
|
47
|
+
subject.async do |task|
|
48
|
+
task.sleep(0.1)
|
49
|
+
task.reactor.stop
|
50
|
+
end
|
51
|
+
|
52
|
+
subject.run
|
53
|
+
|
54
|
+
expect(state).to be == :started
|
42
55
|
end
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
57
|
+
it "can stop reactor from different thread" do
|
58
|
+
events = Thread::Queue.new
|
59
|
+
|
60
|
+
thread = Thread.new do
|
61
|
+
if events.pop
|
62
|
+
subject.stop
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
subject.async do |task|
|
67
|
+
events << true
|
68
|
+
end
|
69
|
+
|
70
|
+
subject.run
|
71
|
+
|
72
|
+
thread.join
|
73
|
+
expect(subject).to be_stopped
|
47
74
|
end
|
48
|
-
|
49
|
-
subject.run
|
50
|
-
|
51
|
-
expect(state).to be == :started
|
52
75
|
end
|
53
|
-
|
54
76
|
it "can't return" do
|
55
77
|
expect do
|
56
78
|
Async::Reactor.run do |task|
|
data/spec/async/wrapper_spec.rb
CHANGED
@@ -21,61 +21,149 @@
|
|
21
21
|
require 'benchmark'
|
22
22
|
|
23
23
|
RSpec.describe Async::Wrapper do
|
24
|
+
include_context Async::RSpec::Reactor
|
25
|
+
|
24
26
|
let(:pipe) {IO.pipe}
|
25
27
|
let(:input) {Async::Wrapper.new(pipe.last)}
|
26
28
|
let(:output) {Async::Wrapper.new(pipe.first)}
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
+
after(:each) do
|
31
|
+
input.close unless input.closed?
|
32
|
+
output.close unless output.closed?
|
30
33
|
|
31
|
-
|
32
|
-
|
34
|
+
expect(input.monitor).to be_nil
|
35
|
+
expect(output.monitor).to be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#wait_readable' do
|
39
|
+
it "can wait to be readable" do
|
40
|
+
reader = reactor.async do
|
41
|
+
expect(output.wait_readable).to be_truthy
|
42
|
+
end
|
33
43
|
|
34
|
-
input.
|
35
|
-
|
44
|
+
input.io.write('Hello World')
|
45
|
+
reader.wait
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can timeout if no event occurs" do
|
49
|
+
expect(output.wait_readable(0.1)).to be_falsey
|
36
50
|
end
|
37
51
|
|
38
|
-
it "can wait for readability" do
|
52
|
+
it "can wait for readability in sequential tasks" do
|
39
53
|
reactor.async do
|
40
54
|
input.wait_writable(1)
|
41
55
|
input.io.write('Hello World')
|
42
56
|
end
|
43
57
|
|
44
|
-
|
58
|
+
2.times do
|
59
|
+
reactor.async do
|
60
|
+
expect(output.wait_readable(1)).to be_truthy
|
61
|
+
end.wait
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can be cancelled" do
|
66
|
+
reactor.async do
|
67
|
+
expect do
|
68
|
+
output.wait_readable
|
69
|
+
end.to raise_error(Async::Wrapper::Cancelled)
|
70
|
+
end
|
45
71
|
|
46
|
-
|
47
|
-
|
72
|
+
expect(output.monitor).to_not be_nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#wait_writable' do
|
77
|
+
it "can wait to be writable" do
|
78
|
+
expect(input.wait_writable).to be_truthy
|
48
79
|
end
|
49
80
|
|
50
|
-
it "can
|
81
|
+
it "can be cancelled" do
|
51
82
|
reactor.async do
|
52
|
-
|
83
|
+
expect do
|
84
|
+
input.wait_readable
|
85
|
+
end.to raise_error(Async::Wrapper::Cancelled)
|
86
|
+
end
|
87
|
+
|
88
|
+
expect(input.monitor).to_not be_nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#wait_any" do
|
93
|
+
it "can wait for any events" do
|
94
|
+
reactor.async do
|
95
|
+
input.wait_any(1)
|
53
96
|
input.io.write('Hello World')
|
54
97
|
end
|
55
98
|
|
56
|
-
output.
|
99
|
+
expect(output.wait_readable(1)).to be_truthy
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can wait for readability in one task and writability in another" do
|
103
|
+
reactor.async do
|
104
|
+
expect do
|
105
|
+
input.wait_readable(1)
|
106
|
+
end.to raise_error(Async::Wrapper::Cancelled)
|
107
|
+
end
|
108
|
+
|
109
|
+
expect(input.monitor.interests).to be == :r
|
57
110
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
111
|
+
reactor.async do
|
112
|
+
input.wait_writable
|
113
|
+
|
114
|
+
input.close
|
115
|
+
output.close
|
116
|
+
end.wait
|
117
|
+
end
|
118
|
+
|
119
|
+
it "fails if waiting on from multiple tasks" do
|
120
|
+
input.reactor = reactor
|
121
|
+
|
122
|
+
reactor.async do
|
123
|
+
expect do
|
124
|
+
input.wait_readable
|
125
|
+
end.to raise_error(Async::Wrapper::Cancelled)
|
62
126
|
end
|
63
127
|
|
64
|
-
input.
|
65
|
-
|
128
|
+
expect(input.monitor.interests).to be == :r
|
129
|
+
|
130
|
+
reactor.async do
|
131
|
+
expect do
|
132
|
+
input.wait_readable
|
133
|
+
end.to raise_error(Async::Wrapper::WaitError)
|
134
|
+
end
|
66
135
|
end
|
67
136
|
end
|
68
137
|
|
69
138
|
describe '#reactor=' do
|
70
|
-
include_context Async::RSpec::Reactor
|
71
|
-
|
72
139
|
it 'can assign a wrapper to a reactor' do
|
73
140
|
input.reactor = reactor
|
74
141
|
|
75
142
|
expect(input.reactor).to be == reactor
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'assigns current reactor when waiting for events' do
|
146
|
+
input.wait_writable
|
76
147
|
|
148
|
+
expect(input.reactor).to be == reactor
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#close' do
|
153
|
+
it "closes monitor when closing wrapper" do
|
154
|
+
input.wait_writable
|
155
|
+
expect(input.monitor).to_not be_nil
|
156
|
+
input.close
|
157
|
+
expect(input.monitor).to be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it "can't wait on closed wrapper" do
|
77
161
|
input.close
|
78
162
|
output.close
|
163
|
+
|
164
|
+
expect do
|
165
|
+
output.wait_readable
|
166
|
+
end.to raise_error(IOError, /closed stream/)
|
79
167
|
end
|
80
168
|
end
|
81
169
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nio4r
|
@@ -120,7 +120,8 @@ files:
|
|
120
120
|
- lib/async.rb
|
121
121
|
- lib/async/clock.rb
|
122
122
|
- lib/async/condition.rb
|
123
|
-
- lib/async/debug.rb
|
123
|
+
- lib/async/debug/monitor.rb
|
124
|
+
- lib/async/debug/selector.rb
|
124
125
|
- lib/async/logger.rb
|
125
126
|
- lib/async/measure.rb
|
126
127
|
- lib/async/node.rb
|