async 1.26.1 → 1.26.2
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 +4 -4
- data/lib/async/barrier.rb +1 -1
- data/lib/async/version.rb +1 -1
- metadata +54 -99
- 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 -133
- 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 -39
- 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
|