async 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "0.13.0"
22
+ VERSION = "0.14.0"
23
23
  end
@@ -22,58 +22,74 @@ module Async
22
22
  # Represents an asynchronous IO within a reactor.
23
23
  class Wrapper
24
24
  # @param io the native object to wrap.
25
- # @param task [Task] the task that is managing this wrapper.
26
- def initialize(io, task)
25
+ # @param reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
26
+ # @param bound [Boolean] whether the underlying socket will be closed if the wrapper is closed.
27
+ def initialize(io, reactor = nil)
27
28
  @io = io
28
- @task = task
29
+
30
+ @reactor = reactor || Task.current.reactor
29
31
  @monitor = nil
30
32
  end
31
33
 
32
34
  # The underlying native `io`.
33
35
  attr :io
34
36
 
35
- # The task this wrapper is associated with.
36
- attr :task
37
+ # The reactor this wrapper is associated with.
38
+ attr :reactor
37
39
 
38
40
  # Wait for the io to become readable.
39
- def wait_readable
40
- wait_any(:r)
41
+ def wait_readable(duration = nil)
42
+ wait_any(:r, duration)
41
43
  end
42
44
 
43
45
  # Wait for the io to become writable.
44
- def wait_writable
45
- wait_any(:w)
46
+ def wait_writable(duration = nil)
47
+ wait_any(:w, duration)
46
48
  end
47
49
 
48
50
  # Wait fo the io to become either readable or writable.
49
51
  # @param interests [:r | :w | :rw] what events to wait for.
50
- def wait_any(interests = :rw)
51
- monitor(interests) do
52
- Task.yield
53
- end
52
+ # @param duration [Float] timeout after the given duration if not `nil`.
53
+ def wait_any(interests = :rw, duration = nil)
54
+ monitor(interests, duration)
54
55
  end
55
56
 
56
57
  # Close the monitor.
57
58
  def close
58
- @monitor.close if @monitor
59
- @monitor = nil
59
+ close_monitor
60
+
61
+ @io.close if @io
60
62
  end
61
63
 
62
64
  private
63
65
 
66
+ def close_monitor
67
+ if @monitor
68
+ @monitor.close
69
+ @monitor = nil
70
+ end
71
+ end
72
+
64
73
  # Monitor the io for the given events
65
- # @param interests [:r | :w | :rw] what events to wait for.
66
- def monitor(interests)
74
+ def monitor(interests, duration = nil)
67
75
  unless @monitor
68
- @monitor = @task.register(@io, interests)
76
+ @monitor = @reactor.register(@io, interests)
69
77
  else
70
78
  @monitor.interests = interests
71
79
  end
72
80
 
73
81
  @monitor.value = Fiber.current
74
82
 
75
- yield
83
+ # If the user requested an explicit timeout for this operation:
84
+ if duration
85
+ @reactor.timeout(duration) do
86
+ Task.yield
87
+ end
88
+ else
89
+ Task.yield
90
+ end
76
91
 
92
+ return true
77
93
  ensure
78
94
  @monitor.value = nil if @monitor
79
95
  end
@@ -21,7 +21,7 @@
21
21
  require 'async/condition'
22
22
 
23
23
  RSpec.describe Async::Condition do
24
- include_context 'reactor'
24
+ include_context Async::RSpec::Reactor
25
25
 
26
26
  it 'should continue after condition is signalled' do
27
27
  task = reactor.async do
@@ -44,6 +44,23 @@ RSpec.describe Async::Node do
44
44
  end
45
45
  end
46
46
 
47
+ describe '#print_hierarchy' do
48
+ let(:buffer) {StringIO.new}
49
+ let(:output) {buffer.string}
50
+ let(:lines) {output.lines}
51
+
52
+ let!(:child) {Async::Node.new(subject)}
53
+
54
+ it "can print hierarchy to bufffer" do
55
+ subject.print_hierarchy(buffer)
56
+
57
+ expect(lines.count).to be 2
58
+
59
+ expect(lines[0]).to be =~ /#<Async::Node:0x\h+>\n/
60
+ expect(lines[1]).to be =~ /\t#<Async::Node:0x\h+>\n/
61
+ end
62
+ end
63
+
47
64
  describe '#consume' do
48
65
  let(:middle) {Async::Node.new(subject)}
49
66
  let(:bottom) {Async::Node.new(middle)}
@@ -56,4 +73,24 @@ RSpec.describe Async::Node do
56
73
  expect(bottom.parent).to be middle
57
74
  end
58
75
  end
76
+
77
+ describe '#annotate' do
78
+ let(:annotation) {'reticulating splines'}
79
+
80
+ it "should have no annotation by default" do
81
+ expect(subject.annotation).to be_nil
82
+ end
83
+
84
+ it 'should output annotation when invoking #to_s' do
85
+ subject.annotate(annotation) do
86
+ expect(subject.to_s).to include(annotation)
87
+ end
88
+ end
89
+
90
+ it 'can assign annotation' do
91
+ subject.annotate(annotation)
92
+
93
+ expect(subject.annotation).to be == annotation
94
+ end
95
+ end
59
96
  end
@@ -20,7 +20,7 @@
20
20
 
21
21
  RSpec.describe Async::Reactor do
22
22
  describe '::run (in existing reactor)' do
23
- include_context "reactor"
23
+ include_context Async::RSpec::Reactor
24
24
 
25
25
  it "should nest reactor" do
26
26
  outer_reactor = Async::Task.current.reactor
@@ -41,7 +41,12 @@ RSpec.describe Async::Reactor do
41
41
  state = :stopped
42
42
  end
43
43
 
44
- subject.stop
44
+ subject.async do |task|
45
+ task.sleep(0.1)
46
+ task.reactor.stop
47
+ end
48
+
49
+ subject.run
45
50
 
46
51
  expect(state).to be == :started
47
52
  end
@@ -62,4 +67,20 @@ RSpec.describe Async::Reactor do
62
67
 
63
68
  expect{reactor.run}.to raise_error(RuntimeError, /closed/)
64
69
  end
70
+
71
+ describe '#async' do
72
+ include_context Async::RSpec::Reactor
73
+
74
+ it "can pass in arguments" do
75
+ reactor.async(:arg) do |task, arg|
76
+ expect(arg).to be == :arg
77
+ end.wait
78
+ end
79
+ end
80
+
81
+ describe '#to_s' do
82
+ it "shows stopped=" do
83
+ expect(subject.to_s).to include "stopped"
84
+ end
85
+ end
65
86
  end
@@ -23,6 +23,39 @@ require 'benchmark'
23
23
  RSpec.describe Async::Task do
24
24
  let(:reactor) {Async::Reactor.new}
25
25
 
26
+ describe '#run' do
27
+ it "can't be invoked twice" do
28
+ task = reactor.async do |task|
29
+ end
30
+
31
+ expect{task.run}.to raise_error(RuntimeError, /already running/)
32
+ end
33
+ end
34
+
35
+ describe '#async' do
36
+ it "can start child async tasks" do
37
+ child = nil
38
+
39
+ parent = reactor.async do |task|
40
+ child = task.async do
41
+ task.sleep(1)
42
+ end
43
+ end
44
+
45
+ expect(parent).to_not be_nil
46
+ expect(child).to_not be_nil
47
+ expect(child.parent).to be parent
48
+ end
49
+
50
+ it "can pass in arguments" do
51
+ reactor.async do |task|
52
+ task.async(:arg) do |task, arg|
53
+ expect(arg).to be == :arg
54
+ end.wait
55
+ end.wait
56
+ end
57
+ end
58
+
26
59
  describe '#stop' do
27
60
  it "can be stopped" do
28
61
  state = nil
@@ -95,7 +128,9 @@ RSpec.describe Async::Task do
95
128
  reactor.run
96
129
  end
97
130
 
98
- expect(time).to be_within(50).percent_of(duration)
131
+ # This is too unstable on travis.
132
+ # expect(time).to be_within(50).percent_of(duration)
133
+ expect(time).to be >= duration
99
134
  expect(state).to be == :finished
100
135
  end
101
136
  end
@@ -182,4 +217,21 @@ RSpec.describe Async::Task do
182
217
  expect(innocent_task).to be_finished
183
218
  end
184
219
  end
220
+
221
+ describe '#to_s' do
222
+ it "should show running" do
223
+ apples_task = reactor.async do |task|
224
+ task.sleep(0.1)
225
+ end
226
+
227
+ expect(apples_task.to_s).to include "running"
228
+ end
229
+
230
+ it "should show complete" do
231
+ apples_task = reactor.async do |task|
232
+ end
233
+
234
+ expect(apples_task.to_s).to include "complete"
235
+ end
236
+ end
185
237
  end
@@ -18,17 +18,33 @@
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_relative 'socket'
21
+ require 'benchmark'
22
22
 
23
- module Async
24
- # Asynchronous UDP socket wrapper.
25
- class UDPSocket < IPSocket
26
- wraps ::UDPSocket
23
+ RSpec.describe Async::Wrapper do
24
+ let(:pipe) {IO.pipe}
25
+ let(:input) {Async::Wrapper.new(pipe.last)}
26
+ let(:output) {Async::Wrapper.new(pipe.first)}
27
+
28
+ describe '#wait_*' do
29
+ include_context Async::RSpec::Reactor
27
30
 
28
- # We pass `send` through directly, but in theory it might block. Internally, it uses sendto.
29
- def_delegators :@io, :send
31
+ it "can wait for writability" do
32
+ expect(input.wait_writable(1)).to be_truthy
33
+
34
+ input.close
35
+ output.close
36
+ end
30
37
 
31
- wrap_blocking_method :recvfrom, :recvfrom_nonblock
38
+ it "can wait for readability" do
39
+ reactor.async do
40
+ input.wait_writable(1)
41
+ input.io.write('Hello World')
42
+ end
43
+
44
+ expect(output.wait_readable(1)).to be_truthy
45
+
46
+ input.close
47
+ output.close
48
+ end
32
49
  end
33
50
  end
34
-
@@ -18,42 +18,9 @@ end
18
18
 
19
19
  require "bundler/setup"
20
20
  require "async"
21
- require "async/tcp_socket"
22
- require "async/udp_socket"
23
21
 
24
- RSpec.shared_context "closes all io" do
25
- def current_ios(gc: GC.start)
26
- all_ios = ObjectSpace.each_object(IO).to_a.sort_by(&:object_id)
27
-
28
- # We are not interested in ios that have been closed already:
29
- return all_ios.reject{|io| io.closed?}
30
- end
31
-
32
- # We use around(:each) because it's the highest priority.
33
- around(:each) do |example|
34
- @system_ios = current_ios
35
-
36
- result = example.run
37
-
38
- expect(current_ios).to be == @system_ios
39
-
40
- result
41
- end
42
- end
43
-
44
- RSpec.shared_context "reactor" do
45
- let(:reactor) {Async::Task.current.reactor}
46
-
47
- around(:each) do |example|
48
- Async::Reactor.run do
49
- result = example.run
50
-
51
- return result if result.is_a? Exception
52
- end
53
- end
54
-
55
- include_context "closes all io"
56
- end
22
+ # Shared rspec helpers:
23
+ require "async/rspec"
57
24
 
58
25
  RSpec.configure do |config|
59
26
  # Enable flags like --only-failures and --next-failure
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: 0.13.0
4
+ version: 0.14.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: 2017-04-17 00:00:00.000000000 Z
11
+ date: 2017-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -39,47 +39,47 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.1'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: async-rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.3'
47
+ version: '1.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.3'
54
+ version: '1.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: process-daemon
56
+ name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.0
61
+ version: '1.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.0
68
+ version: '1.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 3.4.0
75
+ version: '3.4'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 3.4.0
82
+ version: '3.4'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -113,19 +113,12 @@ files:
113
113
  - README.md
114
114
  - Rakefile
115
115
  - async.gemspec
116
- - examples/aio.rb
117
- - examples/echo.rb
118
116
  - lib/async.rb
119
117
  - lib/async/condition.rb
120
- - lib/async/io.rb
121
118
  - lib/async/logger.rb
122
119
  - lib/async/node.rb
123
120
  - lib/async/reactor.rb
124
- - lib/async/socket.rb
125
121
  - lib/async/task.rb
126
- - lib/async/tcp_socket.rb
127
- - lib/async/udp_socket.rb
128
- - lib/async/unix_socket.rb
129
122
  - lib/async/version.rb
130
123
  - lib/async/wrapper.rb
131
124
  - spec/async/condition_spec.rb
@@ -133,9 +126,7 @@ files:
133
126
  - spec/async/reactor/nested_spec.rb
134
127
  - spec/async/reactor_spec.rb
135
128
  - spec/async/task_spec.rb
136
- - spec/async/tcp_socket_spec.rb
137
- - spec/async/udp_socket_spec.rb
138
- - spec/async/unix_socket_spec.rb
129
+ - spec/async/wrapper_spec.rb
139
130
  - spec/spec_helper.rb
140
131
  homepage: https://github.com/socketry/async
141
132
  licenses:
@@ -147,9 +138,9 @@ require_paths:
147
138
  - lib
148
139
  required_ruby_version: !ruby/object:Gem::Requirement
149
140
  requirements:
150
- - - ">="
141
+ - - "~>"
151
142
  - !ruby/object:Gem::Version
152
- version: 2.0.0
143
+ version: '2.0'
153
144
  required_rubygems_version: !ruby/object:Gem::Requirement
154
145
  requirements:
155
146
  - - ">="
@@ -167,7 +158,5 @@ test_files:
167
158
  - spec/async/reactor/nested_spec.rb
168
159
  - spec/async/reactor_spec.rb
169
160
  - spec/async/task_spec.rb
170
- - spec/async/tcp_socket_spec.rb
171
- - spec/async/udp_socket_spec.rb
172
- - spec/async/unix_socket_spec.rb
161
+ - spec/async/wrapper_spec.rb
173
162
  - spec/spec_helper.rb