async 0.10.0 → 0.11.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: f28936ef28f1828e6216b5c9bd1186a89f7037c1
4
- data.tar.gz: c1319a57b9fc689dc77f6bea15a682759affa3b5
3
+ metadata.gz: 1747c272a59477827b3ca9e4a3b1a27bbe995b92
4
+ data.tar.gz: 418f2d203b464d91e75b2f0a55a3288927af4418
5
5
  SHA512:
6
- metadata.gz: 2e6ab3277515b79065c95cb9d88a6ffd7f56c86c2cfe1c5ecbe8145807d9d07d658f76dcf6b3c614aea5d4c370ac85f08966ff7fd3e0f549a1383f70aebbd254
7
- data.tar.gz: 42687f2385510e39e7403cdeaf39b487468564295b360f5f1edff07097d06b3e65af8d2c7821ae31fc1d683b53a497a022ea838f0afd3758388b6c23d9a073ad
6
+ metadata.gz: 3a065e73686d2175ec2b6d605fc81b3ab085ae4b3f4e944d10cef60e15abaa202518a37136b88fa3bb142c5ef0947872b4fdcbe181012211c60ebb3de67811cd
7
+ data.tar.gz: ff5236af82d181c055930c64116f7dca549748c148de866a6148305b52ad1bb8a5b70a0038f2fa0d49f23bfe108dd9d096c1cfd862e2151ed45e4d25bb80a758
data/.editorconfig ADDED
@@ -0,0 +1,5 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 4
data/README.md CHANGED
@@ -37,6 +37,55 @@ Or install it yourself as:
37
37
 
38
38
  $ gem install async
39
39
 
40
+ ## Usage
41
+
42
+ Implementing an asynchronous client/server is easy:
43
+
44
+ ```ruby
45
+ #!/usr/bin/env ruby
46
+
47
+ require 'async'
48
+ require 'async/tcp_socket'
49
+
50
+ def echo_server
51
+ Async::Reactor.run do |task|
52
+ # This is a synchronous block within the current task:
53
+ task.with(TCPServer.new('localhost', 9000)) do |server|
54
+
55
+ # This is an asynchronous block within the current reactor:
56
+ task.reactor.with(server.accept) do |client|
57
+ data = client.read(512)
58
+
59
+ task.sleep(rand)
60
+
61
+ client.write(data)
62
+ end while true
63
+ end
64
+ end
65
+ end
66
+
67
+ def echo_client(data)
68
+ Async::Reactor.run do |task|
69
+ Async::TCPServer.connect('localhost', 9000) do |socket|
70
+ socket.write(data)
71
+ puts "echo_client: #{socket.read(512)}"
72
+ end
73
+ end
74
+ end
75
+
76
+ Async::Reactor.run do
77
+ # Start the echo server:
78
+ server = echo_server
79
+
80
+ 5.times.collect do |i|
81
+ echo_client("Hello World #{i}")
82
+ end.each(&:wait) # Wait until all clients are finished.
83
+
84
+ # Terminate the server and all tasks created within it's async scope:
85
+ server.stop
86
+ end
87
+ ```
88
+
40
89
  ## Supported Ruby Versions
41
90
 
42
91
  This library aims to support and is [tested against][travis] the following Ruby
data/async.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.required_ruby_version = ">= 2.2.6"
25
25
 
26
- spec.add_runtime_dependency "nio4r", "~> 2"
26
+ spec.add_runtime_dependency "nio4r"
27
27
  spec.add_runtime_dependency "timers", "~> 4.1"
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 1.3"
data/examples/echo.rb ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async'
4
+ require 'async/tcp_socket'
5
+
6
+ def echo_server
7
+ Async::Reactor.run do |task|
8
+ # This is a synchronous block within the current task:
9
+ task.with(TCPServer.new('localhost', 9000)) do |server|
10
+
11
+ # This is an asynchronous block within the current reactor:
12
+ task.reactor.with(server.accept) do |client|
13
+ data = client.read(512)
14
+
15
+ task.sleep(rand)
16
+
17
+ client.write(data)
18
+ end while true
19
+ end
20
+ end
21
+ end
22
+
23
+ def echo_client(data)
24
+ Async::Reactor.run do |task|
25
+ Async::TCPServer.connect('localhost', 9000) do |socket|
26
+ socket.write(data)
27
+ puts "echo_client: #{socket.read(512)}"
28
+ end
29
+ end
30
+ end
31
+
32
+ Async::Reactor.run do
33
+ server = echo_server
34
+
35
+ 5.times.collect do |i|
36
+ echo_client("Hello World #{i}")
37
+ end.each(&:wait)
38
+
39
+ server.stop
40
+ end
@@ -0,0 +1,44 @@
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 'fiber'
22
+ require 'forwardable'
23
+
24
+ require_relative 'node'
25
+
26
+ module Async
27
+ class Condition
28
+ def initialize
29
+ @waiting = []
30
+ end
31
+
32
+ def wait
33
+ @waiting << Fiber.current
34
+
35
+ Task.yield
36
+ end
37
+
38
+ def signal(value)
39
+ while task = @waiting.pop
40
+ task.resume(value)
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/async/reactor.rb CHANGED
@@ -162,9 +162,7 @@ module Async
162
162
  end
163
163
  end
164
164
 
165
- result = Fiber.yield
166
-
167
- raise result if result.is_a? Exception
165
+ Task.yield
168
166
  ensure
169
167
  timer.cancel if timer
170
168
  end
data/lib/async/task.rb CHANGED
@@ -22,6 +22,7 @@ require 'fiber'
22
22
  require 'forwardable'
23
23
 
24
24
  require_relative 'node'
25
+ require_relative 'condition'
25
26
 
26
27
  module Async
27
28
  class Interrupt < Exception
@@ -30,6 +31,20 @@ module Async
30
31
  class Task < Node
31
32
  extend Forwardable
32
33
 
34
+ def self.yield
35
+ if block_given?
36
+ result = yield
37
+ else
38
+ result = Fiber.yield
39
+ end
40
+
41
+ if result.is_a? Exception
42
+ raise result
43
+ else
44
+ return result
45
+ end
46
+ end
47
+
33
48
  def initialize(ios, reactor)
34
49
  if parent = Task.current?
35
50
  super(parent)
@@ -46,6 +61,8 @@ module Async
46
61
  @status = :running
47
62
  @result = nil
48
63
 
64
+ @condition = nil
65
+
49
66
  @fiber = Fiber.new do
50
67
  set!
51
68
 
@@ -56,7 +73,8 @@ module Async
56
73
  rescue Interrupt
57
74
  @status = :interrupted
58
75
  # Async.logger.debug("Task #{self} interrupted: #{$!}")
59
- rescue Exception
76
+ rescue Exception => error
77
+ @result = error
60
78
  @status = :failed
61
79
  # Async.logger.debug("Task #{self} failed: #{$!}")
62
80
  raise
@@ -84,10 +102,21 @@ module Async
84
102
 
85
103
  def run
86
104
  @fiber.resume
87
-
88
- return @fiber
89
105
  end
90
106
 
107
+ def result
108
+ raise RuntimeError.new("Cannot wait on own fiber") if Fiber.current.equal?(@fiber)
109
+
110
+ if running?
111
+ @condition ||= Condition.new
112
+ @condition.wait
113
+ else
114
+ Task.yield {@result}
115
+ end
116
+ end
117
+
118
+ alias wait result
119
+
91
120
  def stop
92
121
  @children.each(&:stop)
93
122
 
@@ -122,6 +151,10 @@ module Async
122
151
  Thread.current[:async_task]
123
152
  end
124
153
 
154
+ def running?
155
+ @status == :running
156
+ end
157
+
125
158
  # Whether we can remove this node from the reactor graph.
126
159
  def finished?
127
160
  super && @status != :running
@@ -132,6 +165,10 @@ module Async
132
165
  @ios = []
133
166
 
134
167
  consume
168
+
169
+ if @condition
170
+ @condition.signal(@result)
171
+ end
135
172
  end
136
173
 
137
174
  private
data/lib/async/version.rb CHANGED
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "0.10.0"
22
+ VERSION = "0.11.0"
23
23
  end
data/lib/async/wrapper.rb CHANGED
@@ -55,11 +55,7 @@ module Async
55
55
 
56
56
  def wait_any(interests = :rw)
57
57
  monitor(interests) do
58
- # Async.logger.debug "Fiber #{Fiber.current} yielding..."
59
- result = Fiber.yield
60
-
61
- # Async.logger.debug "Fiber #{Fiber.current} resuming with result #{result}..."
62
- raise result if result.is_a? Exception
58
+ Task.yield
63
59
  end
64
60
  end
65
61
 
@@ -133,4 +133,53 @@ RSpec.describe Async::Task do
133
133
  expect(state).to be == :finished
134
134
  end
135
135
  end
136
+
137
+ describe '#wait' do
138
+ it "will wait on another task to complete" do
139
+ result = nil
140
+
141
+ apples_task = reactor.async do |task|
142
+ task.sleep(0.1)
143
+
144
+ :apples
145
+ end
146
+
147
+ oranges_task = reactor.async do |task|
148
+ task.sleep(0.01)
149
+
150
+ :oranges
151
+ end
152
+
153
+ fruit_salad_task = reactor.async do |task|
154
+ result = [apples_task.result, oranges_task.result]
155
+ end
156
+
157
+ reactor.run
158
+
159
+ expect(result).to be == [:apples, :oranges]
160
+ end
161
+
162
+ it "will propagate exceptions" do
163
+ error_task = nil
164
+
165
+ error_task = reactor.async do |task|
166
+ task.sleep(0.1)
167
+
168
+ raise ArgumentError.new("It simply wasn't good enough")
169
+ end
170
+
171
+ innocent_task = reactor.async do |task|
172
+ expect{error_task.result}.to raise_error(ArgumentError, /wasn't good enough/)
173
+ end
174
+
175
+ begin
176
+ reactor.run
177
+ rescue Exception
178
+ retry
179
+ end
180
+
181
+ expect(error_task).to be_finished
182
+ expect(innocent_task).to be_finished
183
+ end
184
+ end
136
185
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -14,16 +14,16 @@ dependencies:
14
14
  name: nio4r
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: timers
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +103,7 @@ executables: []
103
103
  extensions: []
104
104
  extra_rdoc_files: []
105
105
  files:
106
+ - ".editorconfig"
106
107
  - ".gitignore"
107
108
  - ".rspec"
108
109
  - ".travis.yml"
@@ -112,7 +113,9 @@ files:
112
113
  - Rakefile
113
114
  - async.gemspec
114
115
  - examples/aio.rb
116
+ - examples/echo.rb
115
117
  - lib/async.rb
118
+ - lib/async/condition.rb
116
119
  - lib/async/io.rb
117
120
  - lib/async/logger.rb
118
121
  - lib/async/node.rb