async 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +7 -2
- data/Rakefile +21 -1
- data/async.gemspec +3 -3
- data/benchmark/async_vs_lightio.rb +44 -0
- data/examples/async_method.rb +59 -0
- data/examples/sleep_sort.rb +39 -0
- data/lib/async/reactor.rb +10 -8
- data/lib/async/version.rb +1 -1
- data/lib/async/wrapper.rb +41 -38
- data/papers/1982 Grossman.pdf +0 -0
- data/papers/1987 ODell.pdf +0 -0
- data/spec/async/performance_spec.rb +44 -0
- data/spec/async/reactor_spec.rb +11 -1
- metadata +16 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c6eb0c5b8b5f8a6b2a05ce7c6a563d7deacb8a3ce0da0f0a7797ecc8080790b4
|
4
|
+
data.tar.gz: b0028ef539d8d11026bad3cc476b4fc2af5122e1ac5485888c456b9894be2084
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb55a40ee7d6de6c8ed5d32af4ce29c2e8e8395d9cdec05c6a4d8328cb5fe6794e30d743132967a7b5c985b312a8e0ae5daee54ff6766912f2ebfc218f1119ce
|
7
|
+
data.tar.gz: 9b767f9dff4254c65377ffcef840082a3e31dce20d8abdfe85c0aed0e7e3ddce7819fd30eaec0623e7428e51528694dc9208597fc416b79f3b9146357d9a72f9
|
data/.travis.yml
CHANGED
@@ -2,14 +2,19 @@ language: ruby
|
|
2
2
|
sudo: false
|
3
3
|
dist: trusty
|
4
4
|
cache: bundler
|
5
|
+
addons:
|
6
|
+
apt:
|
7
|
+
packages:
|
8
|
+
- bind9
|
9
|
+
|
10
|
+
before_script: sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
|
5
11
|
|
6
12
|
matrix:
|
7
13
|
include:
|
8
|
-
- rvm: 2.0
|
9
|
-
- rvm: 2.1
|
10
14
|
- rvm: 2.2
|
11
15
|
- rvm: 2.3
|
12
16
|
- rvm: 2.4
|
17
|
+
- rvm: 2.5
|
13
18
|
- rvm: jruby-head
|
14
19
|
env: JRUBY_OPTS="--debug -X+O"
|
15
20
|
- rvm: ruby-head
|
data/Rakefile
CHANGED
@@ -3,7 +3,27 @@ require "rspec/core/rake_task"
|
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:test)
|
5
5
|
|
6
|
-
task :default => :test
|
6
|
+
task :default => [:test, :external]
|
7
|
+
|
8
|
+
def clone_and_test(name)
|
9
|
+
sh("git clone https://git@github.com/socketry/#{name}")
|
10
|
+
|
11
|
+
# I tried using `bundle config --local local.async ../` but it simply doesn't work.
|
12
|
+
File.open("#{name}/Gemfile", "a") do |file|
|
13
|
+
file.puts('gem "async", path: "../"')
|
14
|
+
end
|
15
|
+
|
16
|
+
sh("cd #{name} && bundle install && bundle exec rake test")
|
17
|
+
end
|
18
|
+
|
19
|
+
task :external do
|
20
|
+
Bundler.with_clean_env do
|
21
|
+
clone_and_test("async-io")
|
22
|
+
clone_and_test("async-websocket")
|
23
|
+
clone_and_test("async-dns")
|
24
|
+
clone_and_test("falcon")
|
25
|
+
end
|
26
|
+
end
|
7
27
|
|
8
28
|
task :coverage do
|
9
29
|
ENV['COVERAGE'] = 'y'
|
data/async.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
require_relative 'lib/async/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
@@ -21,9 +21,9 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
spec.has_rdoc = "yard"
|
23
23
|
|
24
|
-
spec.required_ruby_version = "
|
24
|
+
spec.required_ruby_version = ">= 2.2.7"
|
25
25
|
|
26
|
-
spec.add_runtime_dependency "nio4r"
|
26
|
+
spec.add_runtime_dependency "nio4r", "~> 2.0"
|
27
27
|
spec.add_runtime_dependency "timers", "~> 4.1"
|
28
28
|
|
29
29
|
spec.add_development_dependency "async-rspec", "~> 1.1"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'async'
|
4
|
+
require 'lightio'
|
5
|
+
|
6
|
+
require 'benchmark/ips'
|
7
|
+
|
8
|
+
def run_async(count = 10000)
|
9
|
+
Async::Reactor.run do |task|
|
10
|
+
tasks = count.times.map do
|
11
|
+
# LightIO::Beam is a thread-like executor, use it instead Thread
|
12
|
+
task.async do |subtask|
|
13
|
+
# do some io operations in beam
|
14
|
+
subtask.sleep(0.0001)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
tasks.each(&:wait)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_lightio(count = 10000)
|
23
|
+
beams = count.times.map do
|
24
|
+
# LightIO::Beam is a thread-like executor, use it instead Thread
|
25
|
+
LightIO::Beam.new do
|
26
|
+
# do some io operations in beam
|
27
|
+
LightIO.sleep(0.0001)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
beams.each(&:join)
|
32
|
+
end
|
33
|
+
|
34
|
+
Benchmark.ips do |benchmark|
|
35
|
+
benchmark.report("lightio") do |count|
|
36
|
+
run_lightio(count)
|
37
|
+
end
|
38
|
+
|
39
|
+
benchmark.report("async") do |count|
|
40
|
+
run_async(count)
|
41
|
+
end
|
42
|
+
|
43
|
+
benchmark.compare!
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/async'
|
4
|
+
|
5
|
+
module Async::Methods
|
6
|
+
def sleep(*args)
|
7
|
+
Async::Task.current.sleep(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def async(name)
|
11
|
+
original_method = self.method(name)
|
12
|
+
|
13
|
+
define_method(name) do |*args|
|
14
|
+
Async::Reactor.run do |task|
|
15
|
+
original_method.call(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def await(&block)
|
21
|
+
block.call.wait
|
22
|
+
end
|
23
|
+
|
24
|
+
def barrier!
|
25
|
+
Async::Task.current.children.each(&:wait)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
include Async::Methods
|
30
|
+
|
31
|
+
async def count_chickens(area_name)
|
32
|
+
3.times do |i|
|
33
|
+
sleep rand
|
34
|
+
|
35
|
+
puts "Found a chicken in the #{area_name}!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
async def find_chicken(areas)
|
40
|
+
puts "Searching for chicken..."
|
41
|
+
|
42
|
+
sleep rand * 5
|
43
|
+
|
44
|
+
return areas.sample
|
45
|
+
end
|
46
|
+
|
47
|
+
async def count_all_chckens
|
48
|
+
# These methods all run at the same time.
|
49
|
+
count_chickens("garden")
|
50
|
+
count_chickens("house")
|
51
|
+
count_chickens("tree")
|
52
|
+
|
53
|
+
# Wait for all previous async work to complete...
|
54
|
+
barrier!
|
55
|
+
|
56
|
+
puts "There was a chicken in the #{find_chicken(["garden", "house", "tree"]).wait}"
|
57
|
+
end
|
58
|
+
|
59
|
+
count_all_chckens
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/async'
|
4
|
+
|
5
|
+
def sleep_sort(items)
|
6
|
+
Async::Reactor.run do |task|
|
7
|
+
# Where to save the sorted items:
|
8
|
+
sorted_items = []
|
9
|
+
|
10
|
+
items.each do |item|
|
11
|
+
# Spawn an async task...
|
12
|
+
task.async do |nested_task|
|
13
|
+
# Which goes to sleep for the specified duration:
|
14
|
+
nested_task.sleep(item)
|
15
|
+
|
16
|
+
# And then appends the item to the sorted array:
|
17
|
+
sorted_items << item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wait for all children to complete.
|
22
|
+
task.children.each(&:wait)
|
23
|
+
|
24
|
+
# Return the result:
|
25
|
+
sorted_items
|
26
|
+
end.wait # Wait for the entire process to complete.
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calling at the top level blocks the thread:
|
30
|
+
puts sleep_sort(5.times.collect{rand}).inspect
|
31
|
+
|
32
|
+
# Calling in your own reactor allows you to control the asynchronus behaviour:
|
33
|
+
Async::Reactor.run do |task|
|
34
|
+
3.times do
|
35
|
+
task.async do
|
36
|
+
puts sleep_sort(5.times.collect{rand}).inspect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/async/reactor.rb
CHANGED
@@ -46,17 +46,15 @@ module Async
|
|
46
46
|
if current = Task.current?
|
47
47
|
reactor = current.reactor
|
48
48
|
|
49
|
-
reactor.async(*args, &block)
|
49
|
+
return reactor.async(*args, &block)
|
50
50
|
else
|
51
51
|
reactor = self.new
|
52
52
|
|
53
53
|
begin
|
54
|
-
reactor.run(*args, &block)
|
54
|
+
return reactor.run(*args, &block)
|
55
55
|
ensure
|
56
56
|
reactor.close
|
57
57
|
end
|
58
|
-
|
59
|
-
return reactor
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
@@ -103,7 +101,11 @@ module Async
|
|
103
101
|
end
|
104
102
|
|
105
103
|
def register(*args)
|
106
|
-
@selector.register(*args)
|
104
|
+
monitor = @selector.register(*args)
|
105
|
+
|
106
|
+
monitor.value = Fiber.current
|
107
|
+
|
108
|
+
return monitor
|
107
109
|
end
|
108
110
|
|
109
111
|
# Stop the reactor at the earliest convenience. Can be called from a different thread safely.
|
@@ -123,7 +125,7 @@ module Async
|
|
123
125
|
@stopped = false
|
124
126
|
|
125
127
|
# Allow the user to kick of the initial async tasks.
|
126
|
-
async(*args, &block) if block_given?
|
128
|
+
initial_task = async(*args, &block) if block_given?
|
127
129
|
|
128
130
|
@timers.wait do |interval|
|
129
131
|
# - nil: no timers
|
@@ -137,7 +139,7 @@ module Async
|
|
137
139
|
|
138
140
|
# If there is nothing to do, then finish:
|
139
141
|
# Async.logger.debug{"[#{self}] @children.empty? = #{@children.empty?} && interval #{interval.inspect}"}
|
140
|
-
return if @children.empty? && interval.nil?
|
142
|
+
return initial_task if @children.empty? && interval.nil?
|
141
143
|
|
142
144
|
# Async.logger.debug{"Selecting with #{@children.count} fibers interval = #{interval.inspect}..."}
|
143
145
|
if monitors = @selector.select(interval)
|
@@ -149,7 +151,7 @@ module Async
|
|
149
151
|
end
|
150
152
|
end until @stopped
|
151
153
|
|
152
|
-
return
|
154
|
+
return initial_task
|
153
155
|
ensure
|
154
156
|
Async.logger.debug{"[#{self} Ensure] Exiting run-loop (stopped: #{@stopped} exception: #{$!.inspect})..."}
|
155
157
|
@stopped = true
|
data/lib/async/version.rb
CHANGED
data/lib/async/wrapper.rb
CHANGED
@@ -29,16 +29,25 @@ module Async
|
|
29
29
|
def initialize(io, reactor = nil)
|
30
30
|
@io = io
|
31
31
|
|
32
|
-
@reactor = reactor
|
32
|
+
@reactor = reactor
|
33
33
|
@monitor = nil
|
34
34
|
end
|
35
35
|
|
36
36
|
# The underlying native `io`.
|
37
37
|
attr :io
|
38
38
|
|
39
|
-
# The reactor this wrapper is associated with.
|
39
|
+
# The reactor this wrapper is associated with, if any.
|
40
40
|
attr :reactor
|
41
41
|
|
42
|
+
# 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
|
+
# 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
|
+
def reactor= reactor
|
45
|
+
@monitor.close if @monitor
|
46
|
+
|
47
|
+
@reactor = reactor
|
48
|
+
@monitor = nil
|
49
|
+
end
|
50
|
+
|
42
51
|
# Wait for the io to become readable.
|
43
52
|
def wait_readable(duration = nil)
|
44
53
|
wait_any(:r, duration)
|
@@ -53,50 +62,46 @@ module Async
|
|
53
62
|
# @param interests [:r | :w | :rw] what events to wait for.
|
54
63
|
# @param duration [Float] timeout after the given duration if not `nil`.
|
55
64
|
def wait_any(interests = :rw, duration = nil)
|
56
|
-
monitor
|
65
|
+
# 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.
|
66
|
+
if @reactor
|
67
|
+
unless @monitor
|
68
|
+
@monitor = @reactor.register(@io, interests)
|
69
|
+
else
|
70
|
+
@monitor.interests = interests
|
71
|
+
@monitor.value = Fiber.current
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
wait_for(@reactor, @monitor, duration)
|
76
|
+
ensure
|
77
|
+
@monitor.remove_interest(@monitor.interests)
|
78
|
+
@monitor.value = nil
|
79
|
+
end
|
80
|
+
else
|
81
|
+
reactor = Task.current.reactor
|
82
|
+
monitor = reactor.register(@io, interests)
|
83
|
+
|
84
|
+
begin
|
85
|
+
wait_for(reactor, monitor, duration)
|
86
|
+
ensure
|
87
|
+
monitor.close
|
88
|
+
end
|
89
|
+
end
|
57
90
|
end
|
58
91
|
|
59
|
-
# Close the monitor.
|
92
|
+
# Close the io and monitor.
|
60
93
|
def close
|
61
|
-
|
94
|
+
@monitor.close if @monitor
|
62
95
|
|
63
|
-
@io.close
|
96
|
+
@io.close
|
64
97
|
end
|
65
98
|
|
66
99
|
private
|
67
100
|
|
68
|
-
def
|
69
|
-
if @monitor
|
70
|
-
@monitor.close
|
71
|
-
@monitor = nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
if ::NIO::VERSION >= "2.0"
|
76
|
-
def clear_monitor
|
77
|
-
if @monitor
|
78
|
-
# Alas, @monitor.interests = nil does not yet work.
|
79
|
-
@monitor.value = nil
|
80
|
-
@monitor.remove_interest(@monitor.interests)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
else
|
84
|
-
alias clear_monitor close_monitor
|
85
|
-
end
|
86
|
-
|
87
|
-
# Monitor the io for the given events
|
88
|
-
def monitor(interests, duration = nil)
|
89
|
-
unless @monitor
|
90
|
-
@monitor = @reactor.register(@io, interests)
|
91
|
-
else
|
92
|
-
@monitor.interests = interests
|
93
|
-
end
|
94
|
-
|
95
|
-
@monitor.value = Fiber.current
|
96
|
-
|
101
|
+
def wait_for(reactor, monitor, duration)
|
97
102
|
# If the user requested an explicit timeout for this operation:
|
98
103
|
if duration
|
99
|
-
|
104
|
+
reactor.timeout(duration) do
|
100
105
|
Task.yield
|
101
106
|
end
|
102
107
|
else
|
@@ -104,8 +109,6 @@ module Async
|
|
104
109
|
end
|
105
110
|
|
106
111
|
return true
|
107
|
-
ensure
|
108
|
-
clear_monitor
|
109
112
|
end
|
110
113
|
end
|
111
114
|
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'benchmark/ips'
|
3
|
+
|
4
|
+
RSpec.describe Async::Wrapper do
|
5
|
+
let(:pipe) {IO.pipe}
|
6
|
+
|
7
|
+
let(:input) {described_class.new(pipe.first)}
|
8
|
+
let(:output) {described_class.new(pipe.last)}
|
9
|
+
|
10
|
+
it "should be fast to parse large documents" do
|
11
|
+
Benchmark.ips do |x|
|
12
|
+
x.report('Wrapper#wait_readable') do |repeats|
|
13
|
+
Async::Reactor.run do |task|
|
14
|
+
input = Async::Wrapper.new(pipe.first, task.reactor)
|
15
|
+
output = pipe.last
|
16
|
+
|
17
|
+
repeats.times do
|
18
|
+
output.write(".")
|
19
|
+
input.wait_readable
|
20
|
+
input.io.read(1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
x.report('Reactor#register') do |repeats|
|
26
|
+
Async::Reactor.run do |task|
|
27
|
+
input = pipe.first
|
28
|
+
monitor = task.reactor.register(input, :r)
|
29
|
+
output = pipe.last
|
30
|
+
|
31
|
+
repeats.times do
|
32
|
+
output.write(".")
|
33
|
+
Async::Task.yield
|
34
|
+
input.read(1)
|
35
|
+
end
|
36
|
+
|
37
|
+
monitor.close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
x.compare!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/async/reactor_spec.rb
CHANGED
@@ -60,7 +60,10 @@ RSpec.describe Async::Reactor do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
it "is closed after running" do
|
63
|
-
reactor =
|
63
|
+
reactor = nil
|
64
|
+
|
65
|
+
Async::Reactor.run do |task|
|
66
|
+
reactor = task.reactor
|
64
67
|
end
|
65
68
|
|
66
69
|
expect(reactor).to be_closed
|
@@ -68,6 +71,13 @@ RSpec.describe Async::Reactor do
|
|
68
71
|
expect{reactor.run}.to raise_error(RuntimeError, /closed/)
|
69
72
|
end
|
70
73
|
|
74
|
+
it "should return a task" do
|
75
|
+
result = Async::Reactor.run do |task|
|
76
|
+
end
|
77
|
+
|
78
|
+
expect(result).to be_kind_of(Async::Task)
|
79
|
+
end
|
80
|
+
|
71
81
|
describe '#async' do
|
72
82
|
include_context Async::RSpec::Reactor
|
73
83
|
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.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-
|
11
|
+
date: 2018-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nio4r
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '2.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: '0'
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: timers
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,6 +113,9 @@ files:
|
|
113
113
|
- README.md
|
114
114
|
- Rakefile
|
115
115
|
- async.gemspec
|
116
|
+
- benchmark/async_vs_lightio.rb
|
117
|
+
- examples/async_method.rb
|
118
|
+
- examples/sleep_sort.rb
|
116
119
|
- lib/async.rb
|
117
120
|
- lib/async/condition.rb
|
118
121
|
- lib/async/logger.rb
|
@@ -124,8 +127,11 @@ files:
|
|
124
127
|
- lib/async/wrapper.rb
|
125
128
|
- logo.png
|
126
129
|
- logo.svg
|
130
|
+
- papers/1982 Grossman.pdf
|
131
|
+
- papers/1987 ODell.pdf
|
127
132
|
- spec/async/condition_spec.rb
|
128
133
|
- spec/async/node_spec.rb
|
134
|
+
- spec/async/performance_spec.rb
|
129
135
|
- spec/async/reactor/nested_spec.rb
|
130
136
|
- spec/async/reactor_spec.rb
|
131
137
|
- spec/async/task_spec.rb
|
@@ -141,9 +147,9 @@ require_paths:
|
|
141
147
|
- lib
|
142
148
|
required_ruby_version: !ruby/object:Gem::Requirement
|
143
149
|
requirements:
|
144
|
-
- - "
|
150
|
+
- - ">="
|
145
151
|
- !ruby/object:Gem::Version
|
146
|
-
version:
|
152
|
+
version: 2.2.7
|
147
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
154
|
requirements:
|
149
155
|
- - ">="
|
@@ -151,13 +157,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
157
|
version: '0'
|
152
158
|
requirements: []
|
153
159
|
rubyforge_project:
|
154
|
-
rubygems_version: 2.6
|
160
|
+
rubygems_version: 2.7.6
|
155
161
|
signing_key:
|
156
162
|
specification_version: 4
|
157
163
|
summary: Async is an asynchronous I/O framework based on nio4r.
|
158
164
|
test_files:
|
159
165
|
- spec/async/condition_spec.rb
|
160
166
|
- spec/async/node_spec.rb
|
167
|
+
- spec/async/performance_spec.rb
|
161
168
|
- spec/async/reactor/nested_spec.rb
|
162
169
|
- spec/async/reactor_spec.rb
|
163
170
|
- spec/async/task_spec.rb
|