async 1.25.2 → 1.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/async/barrier.rb +1 -1
  3. data/lib/async/clock.rb +33 -1
  4. data/lib/async/logger.rb +1 -6
  5. data/lib/async/node.rb +20 -2
  6. data/lib/async/queue.rb +5 -1
  7. data/lib/async/reactor.rb +73 -12
  8. data/lib/async/scheduler.rb +112 -0
  9. data/lib/async/task.rb +11 -3
  10. data/lib/async/version.rb +1 -1
  11. metadata +46 -104
  12. data/.editorconfig +0 -6
  13. data/.github/workflows/development.yml +0 -55
  14. data/.gitignore +0 -14
  15. data/.rspec +0 -3
  16. data/.yardopts +0 -1
  17. data/Gemfile +0 -20
  18. data/Guardfile +0 -14
  19. data/README.md +0 -385
  20. data/Rakefile +0 -40
  21. data/async.gemspec +0 -34
  22. data/bake.rb +0 -33
  23. data/benchmark/async_vs_lightio.rb +0 -84
  24. data/benchmark/fiber_count.rb +0 -10
  25. data/benchmark/rubies/README.md +0 -51
  26. data/benchmark/rubies/benchmark.rb +0 -220
  27. data/benchmark/thread_count.rb +0 -9
  28. data/benchmark/thread_vs_fiber.rb +0 -45
  29. data/examples/async_method.rb +0 -60
  30. data/examples/callback/loop.rb +0 -44
  31. data/examples/capture/README.md +0 -59
  32. data/examples/capture/capture.rb +0 -116
  33. data/examples/fibers.rb +0 -178
  34. data/examples/queue/producer.rb +0 -28
  35. data/examples/sleep_sort.rb +0 -40
  36. data/examples/stop/condition.rb +0 -31
  37. data/examples/stop/sleep.rb +0 -42
  38. data/gems/event.gemfile +0 -4
  39. data/logo.png +0 -0
  40. data/logo.svg +0 -64
  41. data/papers/1982 Grossman.pdf +0 -0
  42. data/papers/1987 ODell.pdf +0 -0
  43. data/spec/async/barrier_spec.rb +0 -116
  44. data/spec/async/chainable_async_examples.rb +0 -13
  45. data/spec/async/clock_spec.rb +0 -37
  46. data/spec/async/condition_examples.rb +0 -105
  47. data/spec/async/condition_spec.rb +0 -72
  48. data/spec/async/logger_spec.rb +0 -65
  49. data/spec/async/node_spec.rb +0 -193
  50. data/spec/async/notification_spec.rb +0 -66
  51. data/spec/async/performance_spec.rb +0 -72
  52. data/spec/async/queue_spec.rb +0 -129
  53. data/spec/async/reactor/nested_spec.rb +0 -52
  54. data/spec/async/reactor_spec.rb +0 -253
  55. data/spec/async/semaphore_spec.rb +0 -169
  56. data/spec/async/task_spec.rb +0 -476
  57. data/spec/async/wrapper_spec.rb +0 -203
  58. data/spec/async_spec.rb +0 -33
  59. data/spec/enumerator_spec.rb +0 -83
  60. data/spec/kernel/async_spec.rb +0 -33
  61. data/spec/kernel/sync_spec.rb +0 -54
  62. 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
@@ -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
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- fibers = []
4
-
5
- (1..).each do |i|
6
- fibers << Fiber.new{}
7
- fibers.last.resume
8
- puts i
9
- end
10
-
@@ -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