async 1.25.2 → 1.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/barrier.rb +1 -1
- data/lib/async/clock.rb +33 -1
- data/lib/async/logger.rb +1 -6
- data/lib/async/node.rb +20 -2
- data/lib/async/queue.rb +5 -1
- data/lib/async/reactor.rb +73 -12
- data/lib/async/scheduler.rb +112 -0
- data/lib/async/task.rb +11 -3
- data/lib/async/version.rb +1 -1
- metadata +46 -104
- data/.editorconfig +0 -6
- data/.github/workflows/development.yml +0 -55
- data/.gitignore +0 -14
- data/.rspec +0 -3
- data/.yardopts +0 -1
- data/Gemfile +0 -20
- data/Guardfile +0 -14
- data/README.md +0 -385
- data/Rakefile +0 -40
- data/async.gemspec +0 -34
- data/bake.rb +0 -33
- data/benchmark/async_vs_lightio.rb +0 -84
- data/benchmark/fiber_count.rb +0 -10
- data/benchmark/rubies/README.md +0 -51
- data/benchmark/rubies/benchmark.rb +0 -220
- data/benchmark/thread_count.rb +0 -9
- data/benchmark/thread_vs_fiber.rb +0 -45
- data/examples/async_method.rb +0 -60
- data/examples/callback/loop.rb +0 -44
- data/examples/capture/README.md +0 -59
- data/examples/capture/capture.rb +0 -116
- data/examples/fibers.rb +0 -178
- data/examples/queue/producer.rb +0 -28
- data/examples/sleep_sort.rb +0 -40
- data/examples/stop/condition.rb +0 -31
- data/examples/stop/sleep.rb +0 -42
- data/gems/event.gemfile +0 -4
- data/logo.png +0 -0
- data/logo.svg +0 -64
- data/papers/1982 Grossman.pdf +0 -0
- data/papers/1987 ODell.pdf +0 -0
- data/spec/async/barrier_spec.rb +0 -116
- data/spec/async/chainable_async_examples.rb +0 -13
- data/spec/async/clock_spec.rb +0 -37
- data/spec/async/condition_examples.rb +0 -105
- data/spec/async/condition_spec.rb +0 -72
- data/spec/async/logger_spec.rb +0 -65
- data/spec/async/node_spec.rb +0 -193
- data/spec/async/notification_spec.rb +0 -66
- data/spec/async/performance_spec.rb +0 -72
- data/spec/async/queue_spec.rb +0 -129
- data/spec/async/reactor/nested_spec.rb +0 -52
- data/spec/async/reactor_spec.rb +0 -253
- data/spec/async/semaphore_spec.rb +0 -169
- data/spec/async/task_spec.rb +0 -476
- data/spec/async/wrapper_spec.rb +0 -203
- data/spec/async_spec.rb +0 -33
- data/spec/enumerator_spec.rb +0 -83
- data/spec/kernel/async_spec.rb +0 -33
- data/spec/kernel/sync_spec.rb +0 -54
- data/spec/spec_helper.rb +0 -18
data/Rakefile
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
require "rspec/core/rake_task"
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:test)
|
7
|
-
|
8
|
-
task :default => :test
|
9
|
-
|
10
|
-
def clone_and_test(name)
|
11
|
-
path = "external/#{name}"
|
12
|
-
FileUtils.rm_rf path
|
13
|
-
FileUtils.mkdir_p path
|
14
|
-
|
15
|
-
sh("git clone https://git@github.com/socketry/#{name} #{path}")
|
16
|
-
|
17
|
-
# I tried using `bundle config --local local.async ../` but it simply doesn't work.
|
18
|
-
# system("bundle", "config", "--local", "local.async", __dir__, chdir: path)
|
19
|
-
|
20
|
-
File.open("#{path}/Gemfile", "a") do |file|
|
21
|
-
file.puts('gem "async", path: "../../"')
|
22
|
-
end
|
23
|
-
|
24
|
-
sh("cd #{path} && bundle install && bundle exec rspec")
|
25
|
-
end
|
26
|
-
|
27
|
-
task :external do
|
28
|
-
Bundler.with_clean_env do
|
29
|
-
clone_and_test("async-io")
|
30
|
-
clone_and_test("async-websocket")
|
31
|
-
clone_and_test("async-dns")
|
32
|
-
clone_and_test("async-http")
|
33
|
-
clone_and_test("falcon")
|
34
|
-
clone_and_test("async-rest")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
task :coverage do
|
39
|
-
ENV['COVERAGE'] = 'y'
|
40
|
-
end
|
data/async.gemspec
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
|
2
|
-
require_relative 'lib/async/version'
|
3
|
-
|
4
|
-
Gem::Specification.new do |spec|
|
5
|
-
spec.name = "async"
|
6
|
-
spec.version = Async::VERSION
|
7
|
-
spec.authors = ["Samuel Williams"]
|
8
|
-
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
-
spec.description = <<-EOF
|
10
|
-
Async is a modern concurrency framework for Ruby. It implements the
|
11
|
-
reactor pattern, providing both non-blocking I/O and timer events.
|
12
|
-
EOF
|
13
|
-
spec.summary = "Async is a concurrency framework for Ruby."
|
14
|
-
spec.homepage = "https://github.com/socketry/async"
|
15
|
-
spec.license = "MIT"
|
16
|
-
|
17
|
-
spec.files = `git ls-files`.split($/)
|
18
|
-
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
-
spec.require_paths = ["lib"]
|
21
|
-
|
22
|
-
spec.required_ruby_version = ">= 2.5.0"
|
23
|
-
|
24
|
-
spec.add_runtime_dependency "nio4r", "~> 2.3"
|
25
|
-
spec.add_runtime_dependency "timers", "~> 4.1"
|
26
|
-
spec.add_runtime_dependency "console", "~> 1.0"
|
27
|
-
|
28
|
-
spec.add_development_dependency "async-rspec", "~> 1.1"
|
29
|
-
|
30
|
-
spec.add_development_dependency "covered", "~> 0.10"
|
31
|
-
spec.add_development_dependency "bundler"
|
32
|
-
spec.add_development_dependency "rspec", "~> 3.6"
|
33
|
-
spec.add_development_dependency "bake-bundler"
|
34
|
-
end
|
data/bake.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
def external
|
4
|
-
require 'bundler'
|
5
|
-
|
6
|
-
Bundler.with_clean_env do
|
7
|
-
clone_and_test("async-io")
|
8
|
-
clone_and_test("async-websocket")
|
9
|
-
clone_and_test("async-dns")
|
10
|
-
clone_and_test("async-http")
|
11
|
-
clone_and_test("falcon")
|
12
|
-
clone_and_test("async-rest")
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def clone_and_test(name)
|
19
|
-
path = "external/#{name}"
|
20
|
-
FileUtils.rm_rf path
|
21
|
-
FileUtils.mkdir_p path
|
22
|
-
|
23
|
-
system("git clone https://git@github.com/socketry/#{name} #{path}")
|
24
|
-
|
25
|
-
# I tried using `bundle config --local local.async ../` but it simply doesn't work.
|
26
|
-
# system("bundle", "config", "--local", "local.async", __dir__, chdir: path)
|
27
|
-
|
28
|
-
File.open("#{path}/Gemfile", "a") do |file|
|
29
|
-
file.puts('gem "async", path: "../../"')
|
30
|
-
end
|
31
|
-
|
32
|
-
system("cd #{path} && bundle install && bundle exec rspec")
|
33
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'async'
|
5
|
-
require 'lightio'
|
6
|
-
|
7
|
-
require 'benchmark/ips'
|
8
|
-
|
9
|
-
#
|
10
|
-
# It's hard to know exactly how to interpret these results. When running parallel
|
11
|
-
# instances, resource contention is more likely to be a problem, and yet with
|
12
|
-
# async, the performance between a single task and several tasks is roughly the
|
13
|
-
# same, while in the case of lightio, there is an obvious performance gap.
|
14
|
-
#
|
15
|
-
# The main takeaway is that contention causes issues and if systems are not
|
16
|
-
# designed with that in mind, it will impact performance.
|
17
|
-
#
|
18
|
-
# $ ruby async_vs_lightio.rb
|
19
|
-
# Warming up --------------------------------------
|
20
|
-
# lightio (synchronous)
|
21
|
-
# 2.439k i/100ms
|
22
|
-
# async (synchronous) 2.115k i/100ms
|
23
|
-
# lightio (parallel) 211.000 i/100ms
|
24
|
-
# async (parallel) 449.000 i/100ms
|
25
|
-
# Calculating -------------------------------------
|
26
|
-
# lightio (synchronous)
|
27
|
-
# 64.502k (± 3.9%) i/s - 643.896k in 10.002151s
|
28
|
-
# async (synchronous) 161.195k (± 1.6%) i/s - 1.612M in 10.000976s
|
29
|
-
# lightio (parallel) 49.827k (±17.5%) i/s - 477.704k in 9.999579s
|
30
|
-
# async (parallel) 166.862k (± 6.2%) i/s - 1.662M in 10.000365s
|
31
|
-
#
|
32
|
-
# Comparison:
|
33
|
-
# async (parallel): 166862.3 i/s
|
34
|
-
# async (synchronous): 161194.6 i/s - same-ish: difference falls within error
|
35
|
-
# lightio (synchronous): 64502.5 i/s - 2.59x slower
|
36
|
-
# lightio (parallel): 49827.3 i/s - 3.35x slower
|
37
|
-
|
38
|
-
|
39
|
-
DURATION = 0.000001
|
40
|
-
|
41
|
-
def run_async(count, repeats = 10000)
|
42
|
-
Async::Reactor.run do |task|
|
43
|
-
count.times.map do
|
44
|
-
task.async do |subtask|
|
45
|
-
repeats.times do
|
46
|
-
subtask.sleep(DURATION)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end.each(&:wait)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def run_lightio(count, repeats = 10000)
|
54
|
-
count.times.map do
|
55
|
-
LightIO::Beam.new do
|
56
|
-
repeats.times do
|
57
|
-
LightIO.sleep(DURATION)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end.each(&:join)
|
61
|
-
end
|
62
|
-
|
63
|
-
Benchmark.ips do |benchmark|
|
64
|
-
benchmark.time = 10
|
65
|
-
benchmark.warmup = 2
|
66
|
-
|
67
|
-
benchmark.report("lightio (synchronous)") do |count|
|
68
|
-
run_lightio(1, count)
|
69
|
-
end
|
70
|
-
|
71
|
-
benchmark.report("async (synchronous)") do |count|
|
72
|
-
run_async(1, count)
|
73
|
-
end
|
74
|
-
|
75
|
-
benchmark.report("lightio (parallel)") do |count|
|
76
|
-
run_lightio(32, count/32)
|
77
|
-
end
|
78
|
-
|
79
|
-
benchmark.report("async (parallel)") do |count|
|
80
|
-
run_async(32, count/32)
|
81
|
-
end
|
82
|
-
|
83
|
-
benchmark.compare!
|
84
|
-
end
|
data/benchmark/fiber_count.rb
DELETED
data/benchmark/rubies/README.md
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
# (All) Rubies Benchmark
|
2
|
-
|
3
|
-
This is a simple benchmark, which reads and writes data over a pipe.
|
4
|
-
|
5
|
-
It is designed to work as far back as Ruby 1.9.3 at the expense of code clarity. It also works on JRuby and TruffleRuby.
|
6
|
-
|
7
|
-
## Usage
|
8
|
-
|
9
|
-
The simplest way is to use RVM.
|
10
|
-
|
11
|
-
rvm all do ./benchmark.rb
|
12
|
-
|
13
|
-
## Results
|
14
|
-
|
15
|
-
General improvements.
|
16
|
-
|
17
|
-
ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-linux]
|
18
|
-
#<struct Struct::Tms utime=63.41, stime=7.15, cutime=0.0, cstime=0.0>
|
19
|
-
|
20
|
-
ruby 2.0.0p648 (2015-12-16 revision 53162) [x86_64-linux]
|
21
|
-
#<struct Struct::Tms utime=59.5, stime=6.57, cutime=0.0, cstime=0.0>
|
22
|
-
|
23
|
-
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
|
24
|
-
#<struct Process::Tms utime=40.53, stime=6.87, cutime=0.0, cstime=0.0>
|
25
|
-
|
26
|
-
ruby 2.2.10p489 (2018-03-28 revision 63023) [x86_64-linux]
|
27
|
-
#<struct Process::Tms utime=41.26, stime=6.62, cutime=0.0, cstime=0.0>
|
28
|
-
|
29
|
-
ruby 2.3.8p459 (2018-10-18 revision 65136) [x86_64-linux]
|
30
|
-
#<struct Process::Tms utime=31.85, stime=6.55, cutime=0.0, cstime=0.0>
|
31
|
-
|
32
|
-
ruby 2.4.6p354 (2019-04-01 revision 67394) [x86_64-linux]
|
33
|
-
#<struct Process::Tms utime=41.89, stime=6.72, cutime=0.0, cstime=0.0>
|
34
|
-
|
35
|
-
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
|
36
|
-
#<struct Process::Tms utime=26.446285, stime=6.549777, cutime=0.0, cstime=0.0>
|
37
|
-
|
38
|
-
Native fiber implementation & reduced syscalls (https://bugs.ruby-lang.org/issues/14739).
|
39
|
-
|
40
|
-
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
|
41
|
-
#<struct Process::Tms utime=20.045192, stime=5.5941600000000005, cutime=0.0, cstime=0.0>
|
42
|
-
|
43
|
-
Performance regression (https://bugs.ruby-lang.org/issues/16009).
|
44
|
-
|
45
|
-
ruby 2.7.0preview1 (2019-05-31 trunk c55db6aa271df4a689dc8eb0039c929bf6ed43ff) [x86_64-linux]
|
46
|
-
#<struct Process::Tms utime=25.193268, stime=5.808202, cutime=0.0, cstime=0.0>
|
47
|
-
|
48
|
-
Improve fiber performance using pool alloation strategy (https://bugs.ruby-lang.org/issues/15997).
|
49
|
-
|
50
|
-
ruby 2.7.0dev (2019-10-02T08:19:14Z trunk 9759e3c9f0) [x86_64-linux]
|
51
|
-
#<struct Process::Tms utime=19.110835, stime=5.738776, cutime=0.0, cstime=0.0>
|
@@ -1,220 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'socket'
|
5
|
-
require 'fiber'
|
6
|
-
|
7
|
-
puts
|
8
|
-
puts RUBY_DESCRIPTION
|
9
|
-
|
10
|
-
if RUBY_VERSION < "2.0"
|
11
|
-
class String
|
12
|
-
def b
|
13
|
-
self
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# TODO: make these much larger, see if we're effectively batching
|
19
|
-
# even if we don't mean to...
|
20
|
-
QUERY_TEXT = "STATUS".freeze
|
21
|
-
RESPONSE_TEXT = "OK".freeze
|
22
|
-
|
23
|
-
NUM_WORKERS = (ARGV[0] || 10_000).to_i
|
24
|
-
NUM_REQUESTS = (ARGV[1] || 100).to_i
|
25
|
-
|
26
|
-
# Fiber reactor code taken from
|
27
|
-
# https://www.codeotaku.com/journal/2018-11/fibers-are-the-right-solution/index
|
28
|
-
class Reactor
|
29
|
-
def initialize
|
30
|
-
@readable = {}
|
31
|
-
@writable = {}
|
32
|
-
end
|
33
|
-
|
34
|
-
def run
|
35
|
-
while @readable.any? or @writable.any?
|
36
|
-
readable, writable = IO.select(@readable.keys, @writable.keys, [])
|
37
|
-
|
38
|
-
readable.each do |io|
|
39
|
-
@readable[io].resume
|
40
|
-
end
|
41
|
-
|
42
|
-
writable.each do |io|
|
43
|
-
@writable[io].resume
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def wait_readable(io)
|
49
|
-
@readable[io] = Fiber.current
|
50
|
-
Fiber.yield
|
51
|
-
@readable.delete(io)
|
52
|
-
end
|
53
|
-
|
54
|
-
def wait_writable(io)
|
55
|
-
@writable[io] = Fiber.current
|
56
|
-
Fiber.yield
|
57
|
-
@writable.delete(io)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
class Wrapper
|
62
|
-
def initialize(io, reactor)
|
63
|
-
@io = io
|
64
|
-
@reactor = reactor
|
65
|
-
end
|
66
|
-
|
67
|
-
if RUBY_VERSION >= "2.3"
|
68
|
-
def read_nonblock(length, buffer)
|
69
|
-
while true
|
70
|
-
case result = @io.read_nonblock(length, buffer, exception: false)
|
71
|
-
when :wait_readable
|
72
|
-
@reactor.wait_readable(@io)
|
73
|
-
when :wait_writable
|
74
|
-
@reactor.wait_writable(@io)
|
75
|
-
else
|
76
|
-
return result
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
def write_nonblock(buffer)
|
83
|
-
while true
|
84
|
-
case result = @io.write_nonblock(buffer, exception: false)
|
85
|
-
when :wait_readable
|
86
|
-
@reactor.wait_readable(@io)
|
87
|
-
when :wait_writable
|
88
|
-
@reactor.wait_writable(@io)
|
89
|
-
else
|
90
|
-
return result
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
else
|
95
|
-
def read_nonblock(length, buffer)
|
96
|
-
while true
|
97
|
-
begin
|
98
|
-
return @io.read_nonblock(length, buffer)
|
99
|
-
rescue IO::WaitReadable
|
100
|
-
@reactor.wait_readable(@io)
|
101
|
-
rescue IO::WaitWritable
|
102
|
-
@reactor.wait_writable(@io)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def write_nonblock(buffer)
|
108
|
-
while true
|
109
|
-
begin
|
110
|
-
return @io.write_nonblock(buffer)
|
111
|
-
rescue IO::WaitReadable
|
112
|
-
@reactor.wait_readable(@io)
|
113
|
-
rescue IO::WaitWritable
|
114
|
-
@reactor.wait_writable(@io)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def read(length, buffer = nil)
|
121
|
-
if buffer
|
122
|
-
buffer.clear
|
123
|
-
else
|
124
|
-
buffer = String.new.b
|
125
|
-
end
|
126
|
-
|
127
|
-
result = self.read_nonblock(length - buffer.bytesize, buffer)
|
128
|
-
|
129
|
-
if result == length
|
130
|
-
return result
|
131
|
-
end
|
132
|
-
|
133
|
-
chunk = String.new.b
|
134
|
-
while chunk = self.read_nonblock(length - buffer.bytesize, chunk)
|
135
|
-
buffer << chunk
|
136
|
-
|
137
|
-
break if buffer.bytesize == length
|
138
|
-
end
|
139
|
-
|
140
|
-
return buffer
|
141
|
-
end
|
142
|
-
|
143
|
-
def write(buffer)
|
144
|
-
remaining = buffer.dup
|
145
|
-
|
146
|
-
while true
|
147
|
-
result = self.write_nonblock(remaining)
|
148
|
-
|
149
|
-
if result == remaining.bytesize
|
150
|
-
return buffer.bytesize
|
151
|
-
else
|
152
|
-
remaining = remaining.byteslice(result, remaining.bytesize - result)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
reactor = Reactor.new
|
159
|
-
|
160
|
-
worker_read = []
|
161
|
-
worker_write = []
|
162
|
-
|
163
|
-
master_read = []
|
164
|
-
master_write = []
|
165
|
-
|
166
|
-
workers = []
|
167
|
-
|
168
|
-
# puts "Setting up pipes..."
|
169
|
-
NUM_WORKERS.times do |i|
|
170
|
-
r, w = IO.pipe
|
171
|
-
worker_read.push Wrapper.new(r, reactor)
|
172
|
-
master_write.push Wrapper.new(w, reactor)
|
173
|
-
|
174
|
-
r, w = IO.pipe
|
175
|
-
worker_write.push Wrapper.new(w, reactor)
|
176
|
-
master_read.push Wrapper.new(r, reactor)
|
177
|
-
end
|
178
|
-
|
179
|
-
# puts "Setting up fibers..."
|
180
|
-
NUM_WORKERS.times do |i|
|
181
|
-
f = Fiber.new do
|
182
|
-
# Worker code
|
183
|
-
NUM_REQUESTS.times do |req_num|
|
184
|
-
q = worker_read[i].read(QUERY_TEXT.size)
|
185
|
-
if q != QUERY_TEXT
|
186
|
-
raise "Fail! Expected #{QUERY_TEXT.inspect} but got #{q.inspect} on request #{req_num.inspect}!"
|
187
|
-
end
|
188
|
-
worker_write[i].write(RESPONSE_TEXT)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
workers.push f
|
192
|
-
end
|
193
|
-
|
194
|
-
workers.each { |f| f.resume }
|
195
|
-
|
196
|
-
master_fiber = Fiber.new do
|
197
|
-
NUM_WORKERS.times do |worker_num|
|
198
|
-
f = Fiber.new do
|
199
|
-
NUM_REQUESTS.times do |req_num|
|
200
|
-
master_write[worker_num].write(QUERY_TEXT)
|
201
|
-
buffer = master_read[worker_num].read(RESPONSE_TEXT.size)
|
202
|
-
if buffer != RESPONSE_TEXT
|
203
|
-
raise "Error! Fiber no. #{worker_num} on req #{req_num} expected #{RESPONSE_TEXT.inspect} but got #{buf.inspect}!"
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
f.resume
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
master_fiber.resume
|
212
|
-
|
213
|
-
# puts "Starting reactor..."
|
214
|
-
reactor.run
|
215
|
-
|
216
|
-
# puts "Done, finished all reactor Fibers!"
|
217
|
-
|
218
|
-
puts Process.times
|
219
|
-
|
220
|
-
# Exit
|