async 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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