async 0.9.1 → 0.10.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
  SHA1:
3
- metadata.gz: 0c677beaeb3fb10276210be29052382b077ac6ce
4
- data.tar.gz: bc0719a32fbffef3861f111d7a245c45b6e24786
3
+ metadata.gz: f28936ef28f1828e6216b5c9bd1186a89f7037c1
4
+ data.tar.gz: c1319a57b9fc689dc77f6bea15a682759affa3b5
5
5
  SHA512:
6
- metadata.gz: ad2df57e9e6ee7a4280a1b64c6429f6e8c69c2de40dcdd5cb9091c0e8f8b7debdd9f88d4ff3e049900aa16f97c48a3735630725ff4459db1e240360731844712
7
- data.tar.gz: 1773d262ce191fcf566a385620ae7fd7f880f1e886f99e0f8bc4fd1829662b83e824cc3d694d6d4e37b25622608eff1a3452eb82b95e4f297f3aa9c41553d7d4
6
+ metadata.gz: 2e6ab3277515b79065c95cb9d88a6ffd7f56c86c2cfe1c5ecbe8145807d9d07d658f76dcf6b3c614aea5d4c370ac85f08966ff7fd3e0f549a1383f70aebbd254
7
+ data.tar.gz: 42687f2385510e39e7403cdeaf39b487468564295b360f5f1edff07097d06b3e65af8d2c7821ae31fc1d683b53a497a022ea838f0afd3758388b6c23d9a073ad
data/lib/async/node.rb ADDED
@@ -0,0 +1,68 @@
1
+ # Copyright, 2017, 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 'set'
22
+
23
+ module Async
24
+ class Node
25
+ def initialize(parent = nil)
26
+ @children = Set.new
27
+ @parent = nil
28
+
29
+ if parent
30
+ self.parent = parent
31
+ end
32
+ end
33
+
34
+ attr :parent
35
+ attr :children
36
+
37
+ # Attach this node to an existing parent.
38
+ def parent=(parent)
39
+ return if @parent.equal?(parent)
40
+
41
+ if @parent
42
+ @parent.reap(self)
43
+ @parent = nil
44
+ end
45
+
46
+ if parent
47
+ @parent = parent
48
+ @parent.children << self
49
+ end
50
+ end
51
+
52
+ def finished?
53
+ @children.empty?
54
+ end
55
+
56
+ def consume
57
+ if @parent && finished?
58
+ @parent.reap(self)
59
+ @parent.consume
60
+ @parent = nil
61
+ end
62
+ end
63
+
64
+ def reap(child)
65
+ @children.delete(child)
66
+ end
67
+ end
68
+ end
data/lib/async/reactor.rb CHANGED
@@ -30,23 +30,35 @@ module Async
30
30
  class TimeoutError < RuntimeError
31
31
  end
32
32
 
33
- class Reactor
33
+ class Reactor < Node
34
34
  extend Forwardable
35
35
 
36
36
  def self.run(*args, &block)
37
- reactor = self.new
38
-
39
- reactor.async(*args, &block)
37
+ if current = Task.current?
38
+ reactor = current.reactor
39
+
40
+ reactor.async(*args, &block)
41
+ else
42
+ reactor = self.new
43
+
44
+ begin
45
+ reactor.run(*args, &block)
46
+ ensure
47
+ reactor.close
48
+ end
49
+
50
+ return reactor
51
+ end
40
52
  end
41
53
 
42
54
  def initialize(wrappers: IO)
55
+ super(nil)
56
+
43
57
  @wrappers = wrappers
44
58
 
45
59
  @selector = NIO::Selector.new
46
60
  @timers = Timers::Group.new
47
61
 
48
- @fibers = []
49
-
50
62
  @stopped = true
51
63
  end
52
64
 
@@ -75,10 +87,7 @@ module Async
75
87
  # - Fail at the point of call where possible.
76
88
  # - Execute determinstically where possible.
77
89
  # - Avoid overhead if no blocking operation is performed.
78
- fiber = task.run
79
-
80
- # We only start tracking this if the fiber is still alive:
81
- @fibers << fiber if fiber.alive?
90
+ task.run
82
91
 
83
92
  # Async.logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
84
93
  return task
@@ -93,6 +102,8 @@ module Async
93
102
  end
94
103
 
95
104
  def run(*args, &block)
105
+ raise RuntimeError, 'Reactor has been closed' if @selector.nil?
106
+
96
107
  @stopped = false
97
108
 
98
109
  # Allow the user to kick of the initial async tasks.
@@ -105,16 +116,15 @@ module Async
105
116
  # - +ve: timers waiting to fire
106
117
  interval = 0 if interval && interval < 0
107
118
 
108
- # Async.logger.debug "[#{self} Pre] Updating #{@fibers.count} fibers..."
109
- # Async.logger.debug @fibers.collect{|fiber| [fiber, fiber.alive?]}.inspect
119
+ Async.logger.debug "[#{self} Pre] Updating #{@children.count} children..."
120
+ Async.logger.debug @children.collect{|child| [child.to_s, child.alive?]}.inspect
110
121
  # As timeouts may have been updated, and caused fibers to complete, we should check this.
111
- @fibers.delete_if{|fiber| !fiber.alive?}
112
122
 
113
123
  # If there is nothing to do, then finish:
114
- # Async.logger.debug "[#{self}] @fibers.empty? = #{@fibers.empty?} && interval #{interval.inspect}"
115
- return if @fibers.empty? && interval.nil?
124
+ Async.logger.debug "[#{self}] @children.empty? = #{@children.empty?} && interval #{interval.inspect}"
125
+ return if @children.empty? && interval.nil?
116
126
 
117
- # Async.logger.debug "Selecting with #{@fibers.count} fibers interval = #{interval}..."
127
+ Async.logger.debug "Selecting with #{@children.count} fibers interval = #{interval}..."
118
128
  if monitors = @selector.select(interval)
119
129
  monitors.each do |monitor|
120
130
  if task = monitor.value
@@ -124,12 +134,25 @@ module Async
124
134
  end
125
135
  end
126
136
  end until @stopped
137
+
138
+ return self
127
139
  ensure
128
- # Async.logger.debug "[#{self} Ensure] Exiting run-loop (stopped: #{@stopped} exception: #{$!})..."
129
- # Async.logger.debug @fibers.collect{|fiber| [fiber, fiber.alive?]}.inspect
140
+ Async.logger.debug "[#{self} Ensure] Exiting run-loop (stopped: #{@stopped} exception: #{$!})..."
141
+ Async.logger.debug @children.collect{|child| [child.to_s, child.alive?]}.inspect
130
142
  @stopped = true
131
143
  end
132
144
 
145
+ def close
146
+ @children.each(&:stop)
147
+
148
+ @selector.close
149
+ @selector = nil
150
+ end
151
+
152
+ def closed?
153
+ @selector.nil?
154
+ end
155
+
133
156
  def sleep(duration)
134
157
  task = Fiber.current
135
158
 
data/lib/async/task.rb CHANGED
@@ -21,52 +21,82 @@
21
21
  require 'fiber'
22
22
  require 'forwardable'
23
23
 
24
+ require_relative 'node'
25
+
24
26
  module Async
25
27
  class Interrupt < Exception
26
28
  end
27
29
 
28
- class Task
30
+ class Task < Node
29
31
  extend Forwardable
30
32
 
31
- def initialize(ios, reactor, &block)
33
+ def initialize(ios, reactor)
34
+ if parent = Task.current?
35
+ super(parent)
36
+ else
37
+ super(reactor)
38
+ end
39
+
32
40
  @ios = Hash[
33
41
  ios.collect{|io| [io.fileno, reactor.wrap(io, self)]}
34
42
  ]
35
43
 
36
44
  @reactor = reactor
37
45
 
46
+ @status = :running
47
+ @result = nil
48
+
38
49
  @fiber = Fiber.new do
39
50
  set!
40
51
 
41
52
  begin
42
- yield(*@ios.values, self)
53
+ @result = yield(*@ios.values, self)
54
+ @status = :complete
43
55
  # Async.logger.debug("Task #{self} completed normally.")
44
56
  rescue Interrupt
57
+ @status = :interrupted
45
58
  # Async.logger.debug("Task #{self} interrupted: #{$!}")
59
+ rescue Exception
60
+ @status = :failed
61
+ # Async.logger.debug("Task #{self} failed: #{$!}")
62
+ raise
46
63
  ensure
64
+ # Async.logger.debug("Task #{self} closing: #{$!}")
47
65
  close
48
66
  end
49
67
  end
50
68
  end
51
69
 
70
+ def to_s
71
+ "#{super}[#{@status}]"
72
+ end
73
+
74
+ attr :ios
75
+
76
+ attr :reactor
52
77
  def_delegators :@reactor, :timeout, :sleep
53
78
 
79
+ attr :fiber
80
+ def_delegators :@fiber, :alive?
81
+
82
+ attr :status
83
+ attr :result
84
+
54
85
  def run
55
86
  @fiber.resume
56
87
 
57
88
  return @fiber
58
89
  end
59
90
 
60
- def stop!
91
+ def stop
92
+ @children.each(&:stop)
93
+
61
94
  if @fiber.alive?
62
95
  exception = Interrupt.new("Stop right now!")
63
96
  @fiber.resume(exception)
64
97
  end
65
98
  end
66
99
 
67
- attr :ios
68
- attr :reactor
69
-
70
100
  def with(io)
71
101
  wrapper = @reactor.wrap(io, self)
72
102
 
@@ -88,12 +118,24 @@ module Async
88
118
  Thread.current[:async_task] or raise RuntimeError, "No async task available!"
89
119
  end
90
120
 
91
- private
121
+ def self.current?
122
+ Thread.current[:async_task]
123
+ end
124
+
125
+ # Whether we can remove this node from the reactor graph.
126
+ def finished?
127
+ super && @status != :running
128
+ end
92
129
 
93
130
  def close
94
131
  @ios.each_value(&:close)
132
+ @ios = []
133
+
134
+ consume
95
135
  end
96
136
 
137
+ private
138
+
97
139
  def set!
98
140
  # This is actually fiber-local:
99
141
  Thread.current[:async_task] = self
data/lib/async/version.rb CHANGED
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "0.9.1"
22
+ VERSION = "0.10.0"
23
23
  end
@@ -0,0 +1,59 @@
1
+ # Copyright, 2017, 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 'benchmark'
22
+
23
+ RSpec.describe Async::Node do
24
+ describe '#parent=' do
25
+ let(:child) {Async::Node.new(subject)}
26
+
27
+ it "should construct nested tree" do
28
+ expect(child.parent).to be subject
29
+ expect(subject.children).to include(child)
30
+ end
31
+
32
+ it "should break nested tree" do
33
+ child.parent = nil
34
+
35
+ expect(child.parent).to be_nil
36
+ expect(subject.children).to be_empty
37
+ end
38
+
39
+ it "can consume bottom to top" do
40
+ child.consume
41
+
42
+ expect(child.parent).to be_nil
43
+ expect(subject.children).to be_empty
44
+ end
45
+ end
46
+
47
+ describe '#consume' do
48
+ let(:middle) {Async::Node.new(subject)}
49
+ let(:bottom) {Async::Node.new(middle)}
50
+
51
+ it "can't consume middle node" do
52
+ expect(bottom.parent).to be middle
53
+
54
+ middle.consume
55
+
56
+ expect(bottom.parent).to be middle
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright, 2017, 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
+ RSpec.describe Async::Reactor do
22
+ describe '::run (in existing reactor)' do
23
+ include_context "reactor"
24
+
25
+ it "should nest reactor" do
26
+ outer_reactor = Async::Task.current.reactor
27
+ inner_reactor = nil
28
+
29
+ task = described_class.run do |task|
30
+ inner_reactor = task.reactor
31
+ end
32
+
33
+ expect(outer_reactor).to be_kind_of(described_class)
34
+ expect(outer_reactor).to be_eql(inner_reactor)
35
+ end
36
+ end
37
+
38
+ describe '::run' do
39
+ it "should nest reactor" do
40
+ expect(Async::Task.current?).to be_nil
41
+ inner_reactor = nil
42
+
43
+ task = described_class.run do |task|
44
+ inner_reactor = task.reactor
45
+ end
46
+
47
+ expect(inner_reactor).to be_kind_of(described_class)
48
+ end
49
+ end
50
+ end
@@ -19,18 +19,17 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  RSpec.describe Async::Reactor do
22
- # Shared port for localhost network tests.
23
- let(:port) {6778}
24
-
25
22
  it "can run asynchronously" do
26
23
  outer_fiber = Fiber.current
27
24
  inner_fiber = nil
28
25
 
29
- described_class.run do
26
+ described_class.run do |task|
27
+ task.sleep(0)
30
28
  inner_fiber = Fiber.current
31
29
  end
32
30
 
33
- expect(outer_fiber).not_to be == inner_fiber
31
+ expect(inner_fiber).to_not be nil
32
+ expect(outer_fiber).to_not be == inner_fiber
34
33
  end
35
34
 
36
35
  it "can be stopped" do
@@ -47,127 +46,20 @@ RSpec.describe Async::Reactor do
47
46
  expect(state).to be == :started
48
47
  end
49
48
 
50
- describe 'basic tcp server' do
51
- include_context "reactor"
52
-
53
- # These may block:
54
- let(:server) {TCPServer.new("localhost", port)}
55
- let(:client) {TCPSocket.new("localhost", port)}
56
-
57
- let(:data) {"The quick brown fox jumped over the lazy dog."}
58
-
59
- after(:each) do
60
- server.close
61
- end
62
-
63
- it "should start server and send data" do
64
- subject.async(server) do |server, task|
65
- task.with(server.accept) do |peer|
66
- peer.write(peer.read(512))
67
- end
68
- end
69
-
70
- subject.with(client) do |client|
71
- client.write(data)
72
-
73
- expect(client.read(512)).to be == data
49
+ it "can't return" do
50
+ expect do
51
+ Async::Reactor.run do |task|
52
+ return
74
53
  end
75
-
76
- subject.run
77
-
78
- expect(client).to be_closed
79
- end
54
+ end.to raise_error(LocalJumpError)
80
55
  end
81
56
 
82
- describe 'non-blocking tcp connect' do
83
- include_context "reactor"
84
-
85
- # These may block:
86
- let(:server) {TCPServer.new("localhost", port)}
87
-
88
- let(:data) {"The quick brown fox jumped over the lazy dog."}
89
-
90
- after(:each) do
91
- server.close
92
- end
93
-
94
- it "should start server and send data" do
95
- subject.async(server) do |server, task|
96
- task.with(server.accept) do |peer|
97
- peer.write(peer.read(512))
98
- end
99
- end
100
-
101
- subject.async do |task|
102
- Async::TCPSocket.connect("localhost", port) do |client|
103
- client.write(data)
104
- expect(client.read(512)).to be == data
105
- end
106
- end
107
-
108
- subject.run
57
+ it "is closed after running" do
58
+ reactor = Async::Reactor.run do
109
59
  end
110
60
 
111
- it "can connect socket and read/write in a different task" do
112
- subject.async(server) do |server, task|
113
- task.with(server.accept) do |peer|
114
- peer.write(peer.read(512))
115
- end
116
- end
117
-
118
- socket = nil
119
-
120
- subject.async do |task|
121
- socket = Async::TCPSocket.connect("localhost", port)
122
-
123
- # Stop the reactor once the connection was made.
124
- subject.stop
125
- end
126
-
127
- subject.run
128
-
129
- expect(socket).to_not be_nil
130
-
131
- subject.async(socket) do |client|
132
- client.write(data)
133
- expect(client.read(512)).to be == data
134
- end
135
-
136
- subject.run
137
- end
138
- end
139
-
140
- describe 'basic udp server' do
141
- include_context "reactor"
142
-
143
- # These may block:
144
- let(:server) {UDPSocket.new.tap{|socket| socket.bind("localhost", port)}}
145
- let(:client) {UDPSocket.new}
146
-
147
- let(:data) {"The quick brown fox jumped over the lazy dog."}
148
-
149
- after(:each) do
150
- server.close
151
- end
61
+ expect(reactor).to be_closed
152
62
 
153
- it "should echo data back to peer" do
154
- subject.async(server) do |server, task|
155
- packet, (_, remote_port, remote_host) = server.recvfrom(512)
156
-
157
- reactor.async do
158
- server.send(packet, 0, remote_host, remote_port)
159
- end
160
- end
161
-
162
- subject.async(client) do |client|
163
- client.send(data, 0, "localhost", port)
164
-
165
- response, _ = client.recvfrom(512)
166
-
167
- expect(response).to be == data
168
- end
169
-
170
- subject.run
171
- end
63
+ expect{reactor.run}.to raise_error(RuntimeError, /closed/)
172
64
  end
173
65
  end
@@ -23,7 +23,7 @@ require 'benchmark'
23
23
  RSpec.describe Async::Task do
24
24
  let(:reactor) {Async::Reactor.new}
25
25
 
26
- describe '#stop!' do
26
+ describe '#stop' do
27
27
  it "can be stopped" do
28
28
  state = nil
29
29
 
@@ -33,10 +33,51 @@ RSpec.describe Async::Task do
33
33
  state = :finished
34
34
  end
35
35
 
36
- task.stop!
36
+ task.stop
37
37
 
38
38
  expect(state).to be == :started
39
39
  end
40
+
41
+ it "should kill direct child" do
42
+ parent_task = child_task = nil
43
+
44
+ task = reactor.async do |task|
45
+ parent_task = task
46
+ reactor.async do |task|
47
+ child_task = task
48
+ task.sleep(10)
49
+ end
50
+ task.sleep(10)
51
+ end
52
+
53
+ expect(parent_task).to_not be_nil
54
+ expect(child_task).to_not be_nil
55
+
56
+ expect(parent_task.fiber).to be_alive
57
+ expect(child_task.fiber).to be_alive
58
+
59
+ parent_task.stop
60
+
61
+ expect(parent_task.fiber).to_not be_alive
62
+ expect(child_task.fiber).to_not be_alive
63
+ end
64
+
65
+ it "should not remove running task" do
66
+ top_task = middle_task = bottom_task = nil
67
+
68
+ top_task = reactor.async do |task|
69
+ middle_task = reactor.async do |task|
70
+ bottom_task = reactor.async do |task|
71
+ task.sleep(10)
72
+ end
73
+ task.sleep(10)
74
+ end
75
+ task.sleep(10)
76
+ end
77
+
78
+ bottom_task.stop
79
+ expect(top_task.children).to include(middle_task)
80
+ end
40
81
  end
41
82
 
42
83
  describe '#sleep' do
@@ -0,0 +1,119 @@
1
+ # Copyright, 2017, 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
+ RSpec.describe Async::Reactor do
22
+ # Shared port for localhost network tests.
23
+ let(:port) {6779}
24
+
25
+ def run_echo_server
26
+ # Accept a single incoming connection and then finish.
27
+ subject.async(server) do |server, task|
28
+ task.with(server.accept) do |peer|
29
+ data = peer.read(512)
30
+ peer.write(data)
31
+ end
32
+ end
33
+
34
+ yield
35
+
36
+ subject.run
37
+
38
+ server.close
39
+ end
40
+
41
+ describe 'basic tcp server' do
42
+ # These may block:
43
+ let(:server) {TCPServer.new("localhost", port)}
44
+ let(:client) {TCPSocket.new("localhost", port)}
45
+
46
+ let(:data) {"The quick brown fox jumped over the lazy dog."}
47
+
48
+ it "should start server and send data" do
49
+ run_echo_server do
50
+ subject.with(client) do |client|
51
+ client.write(data)
52
+ expect(client.read(512)).to be == data
53
+ end
54
+ end
55
+
56
+ expect(client).to be_closed
57
+ end
58
+ end
59
+
60
+ describe 'non-blocking tcp connect' do
61
+ # These may block:
62
+ let(:server) {TCPServer.new("localhost", port)}
63
+
64
+ let(:data) {"The quick brown fox jumped over the lazy dog."}
65
+
66
+ it "should start server and send data" do
67
+ run_echo_server do
68
+ subject.async do |task|
69
+ Async::TCPSocket.connect("localhost", port) do |client|
70
+ client.write(data)
71
+ expect(client.read(512)).to be == data
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ it "can connect socket and read/write in a different task" do
78
+ socket = nil
79
+
80
+ run_echo_server do
81
+ subject.async do |task|
82
+ socket = Async::TCPSocket.connect("localhost", port)
83
+
84
+ # Stop the reactor once the connection was made.
85
+ subject.stop
86
+ end
87
+
88
+ subject.run
89
+
90
+ expect(socket).to_not be_nil
91
+
92
+ subject.async(socket) do |client|
93
+ client.write(data)
94
+ expect(client.read(512)).to be == data
95
+ end
96
+
97
+ subject.run
98
+ end
99
+ end
100
+
101
+ it "can't use a socket in nested tasks" do
102
+ socket = nil
103
+
104
+ run_echo_server do
105
+ subject.async do |task|
106
+ socket = Async::TCPSocket.connect("localhost", port)
107
+
108
+ # I'm not sure if this is the right behaviour or not. Without a significant amont of work, async sockets are tied to the task that creates them.
109
+ expect do
110
+ subject.async(socket) do |client|
111
+ client.write(data)
112
+ expect(client.read(512)).to be == data
113
+ end
114
+ end.to raise_error(ArgumentError, /already registered with selector/)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,56 @@
1
+ # Copyright, 2017, 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
+ RSpec.describe Async::Reactor do
22
+ # Shared port for localhost network tests.
23
+ let(:port) {6778}
24
+
25
+ describe 'basic udp server' do
26
+ # These may block:
27
+ let(:server) {UDPSocket.new.tap{|socket| socket.bind("localhost", port)}}
28
+ let(:client) {UDPSocket.new}
29
+
30
+ let(:data) {"The quick brown fox jumped over the lazy dog."}
31
+
32
+ after(:each) do
33
+ server.close
34
+ end
35
+
36
+ it "should echo data back to peer" do
37
+ subject.async(server) do |server, task|
38
+ packet, (_, remote_port, remote_host) = server.recvfrom(512)
39
+
40
+ subject.async do
41
+ server.send(packet, 0, remote_host, remote_port)
42
+ end
43
+ end
44
+
45
+ subject.async(client) do |client|
46
+ client.send(data, 0, "localhost", port)
47
+
48
+ response, _ = client.recvfrom(512)
49
+
50
+ expect(response).to be == data
51
+ end
52
+
53
+ subject.run
54
+ end
55
+ end
56
+ end
data/spec/spec_helper.rb CHANGED
@@ -22,10 +22,10 @@ require "async/tcp_socket"
22
22
  require "async/udp_socket"
23
23
 
24
24
  RSpec.shared_context "reactor" do
25
- let(:reactor) {Async::Reactor.new}
25
+ let(:reactor) {Async::Task.current.reactor}
26
26
 
27
27
  around(:each) do |example|
28
- reactor.run do
28
+ Async::Reactor.run do
29
29
  example.run
30
30
  end
31
31
  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: 0.9.1
4
+ version: 0.10.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-07 00:00:00.000000000 Z
11
+ date: 2017-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -115,6 +115,7 @@ files:
115
115
  - lib/async.rb
116
116
  - lib/async/io.rb
117
117
  - lib/async/logger.rb
118
+ - lib/async/node.rb
118
119
  - lib/async/reactor.rb
119
120
  - lib/async/socket.rb
120
121
  - lib/async/ssl_socket.rb
@@ -124,8 +125,12 @@ files:
124
125
  - lib/async/unix_socket.rb
125
126
  - lib/async/version.rb
126
127
  - lib/async/wrapper.rb
128
+ - spec/async/node_spec.rb
129
+ - spec/async/reactor/nested_spec.rb
127
130
  - spec/async/reactor_spec.rb
128
131
  - spec/async/task_spec.rb
132
+ - spec/async/tcp_socket_spec.rb
133
+ - spec/async/udp_socket_spec.rb
129
134
  - spec/spec_helper.rb
130
135
  homepage: https://github.com/socketry/async
131
136
  licenses:
@@ -152,6 +157,10 @@ signing_key:
152
157
  specification_version: 4
153
158
  summary: Async is an asynchronous I/O framework based on nio4r.
154
159
  test_files:
160
+ - spec/async/node_spec.rb
161
+ - spec/async/reactor/nested_spec.rb
155
162
  - spec/async/reactor_spec.rb
156
163
  - spec/async/task_spec.rb
164
+ - spec/async/tcp_socket_spec.rb
165
+ - spec/async/udp_socket_spec.rb
157
166
  - spec/spec_helper.rb