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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f19a934a9510bb56c88e54ac305707a0cd68d5a74f925e56a2f6ca69c5a0bab9
4
- data.tar.gz: 8c68511531f459a2c067550f369e9253d7c93064f054a08780a11f357d68a942
3
+ metadata.gz: 8342091acbc6da153552ca269239949b27898eb7bff55ca9425687e86f314751
4
+ data.tar.gz: fa8fd142667ba572fd66554d25bf9490625811f91005c9c4b0ac76f9329dba69
5
5
  SHA512:
6
- metadata.gz: e1387cc04e3d358d9b39dc5f2fb59e457b173f04b3381c603a11d495f987f29c41753c6addae55fcb203e6e1a3f96bf929e45cafddf624fd374fc85b6bc8e33f
7
- data.tar.gz: e76e0e5dc3c67f6d77209f501a58ca1c97dbc8f96d40081ec55d082e0f0be63e7ecd344ba4adfcbaadcc910590d1abdf4a4eab0bd4a0ab8310f5afbe5a55db7d
6
+ metadata.gz: f8e9d025de96cee15d9967e344a81dd01e3c3f6e64895f1deba1f2fd77edbbff2d9d7d63294cdaca6492add2112e5b0742e326c70845c52b8041b10eba96fd17
7
+ data.tar.gz: 4f07e3153419bd128c53c86f63fca88dcde579d906689e9bbaff488b6f782f11d3950a1f6754894f20880aa8aa09fcde50b7608c0c47b97ef0fe9314b5d5343b
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ task :external do
21
21
  clone_and_test("async-io")
22
22
  clone_and_test("async-websocket")
23
23
  clone_and_test("async-dns")
24
+ clone_and_test("async-http")
24
25
  clone_and_test("falcon")
25
26
  end
26
27
  end
@@ -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
- class DebugSelector
25
- class MonitorProxy
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
- if monitor = @monitors[io.fileno]
54
- raise RuntimeError, "Trying to register monitor for #{io.inspect} but it was already registered as #{monitor.io.inspect}!"
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 = NIO::Selector.new
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(*args)
116
- monitor = @selector.register(*args)
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
- if fiber = monitor.value
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
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "1.7.0"
22
+ VERSION = "1.8.0"
23
23
  end
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 @monitor
46
- @monitor.close
47
- @monitor = nil
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
- wait_any(:r, duration)
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
- wait_any(:w, duration)
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(interests = :rw, duration = nil)
67
- # There is value in caching this monitor - if you can reuse it, you will get about 2x the throughput, because you avoid calling Reactor#register and Monitor#close for every call. That being said, by caching it, you also introduce lifetime issues. I'm going to accept this overhead into the wrapper design because it's pretty convenient, but if you want faster IO, take a look at the performance spec which compares this method with a more direct alternative.
68
- if @reactor
69
- unless @monitor
70
- @monitor = @reactor.register(@io, interests)
71
- else
72
- @monitor.interests = interests
73
- @monitor.value = Fiber.current
74
- end
75
-
76
- begin
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
- if @monitor
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
- def wait_for(reactor, monitor, duration)
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
- Task.yield
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
@@ -19,29 +19,31 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  RSpec.describe Async.logger do
22
- let!(:debug) {$DEBUG}
23
- after {$DEBUG = debug}
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
- expect(Async.default_log_level).to be == Logger::WARN
33
- end
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
- expect(Async.default_log_level).to be == Logger::DEBUG
39
- end
40
-
41
- it 'should set default log level based on $VERBOSE' do
42
- $DEBUG = false
43
- $VERBOSE = true
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
- expect(Async.default_log_level).to be == Logger::INFO
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
@@ -19,38 +19,60 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  RSpec.describe Async::Reactor do
22
- it "can run asynchronously" do
23
- outer_fiber = Fiber.current
24
- inner_fiber = nil
25
-
26
- described_class.run do |task|
27
- task.sleep(0)
28
- inner_fiber = Fiber.current
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
- it "can be stopped" do
36
- state = nil
37
-
38
- subject.async do |task|
39
- state = :started
40
- task.sleep(10)
41
- state = :stopped
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
- subject.async do |task|
45
- task.sleep(0.1)
46
- task.reactor.stop
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|
@@ -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
- describe '#wait_*' do
29
- include_context Async::RSpec::Reactor
30
+ after(:each) do
31
+ input.close unless input.closed?
32
+ output.close unless output.closed?
30
33
 
31
- it "can wait for writability" do
32
- expect(input.wait_writable(1)).to be_truthy
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.close
35
- output.close
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
- expect(output.wait_readable(1)).to be_truthy
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
- input.close
47
- output.close
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 wait for readability in multiple tasks" do
81
+ it "can be cancelled" do
51
82
  reactor.async do
52
- input.wait_writable(1)
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.reactor = reactor
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
- 2.times do
59
- reactor.async do
60
- expect(output.wait_readable(1)).to be_truthy
61
- end.wait
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.close
65
- output.close
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.7.0
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-27 00:00:00.000000000 Z
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