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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/async/barrier.rb +1 -1
  3. data/lib/async/version.rb +1 -1
  4. metadata +54 -99
  5. data/.editorconfig +0 -6
  6. data/.github/workflows/development.yml +0 -55
  7. data/.gitignore +0 -14
  8. data/.rspec +0 -3
  9. data/.yardopts +0 -1
  10. data/Gemfile +0 -20
  11. data/Guardfile +0 -14
  12. data/README.md +0 -385
  13. data/Rakefile +0 -40
  14. data/async.gemspec +0 -34
  15. data/bake.rb +0 -33
  16. data/benchmark/async_vs_lightio.rb +0 -84
  17. data/benchmark/fiber_count.rb +0 -10
  18. data/benchmark/rubies/README.md +0 -51
  19. data/benchmark/rubies/benchmark.rb +0 -220
  20. data/benchmark/thread_count.rb +0 -9
  21. data/benchmark/thread_vs_fiber.rb +0 -45
  22. data/examples/async_method.rb +0 -60
  23. data/examples/callback/loop.rb +0 -44
  24. data/examples/capture/README.md +0 -59
  25. data/examples/capture/capture.rb +0 -116
  26. data/examples/fibers.rb +0 -178
  27. data/examples/queue/producer.rb +0 -28
  28. data/examples/sleep_sort.rb +0 -40
  29. data/examples/stop/condition.rb +0 -31
  30. data/examples/stop/sleep.rb +0 -42
  31. data/gems/event.gemfile +0 -4
  32. data/logo.png +0 -0
  33. data/logo.svg +0 -64
  34. data/papers/1982 Grossman.pdf +0 -0
  35. data/papers/1987 ODell.pdf +0 -0
  36. data/spec/async/barrier_spec.rb +0 -116
  37. data/spec/async/chainable_async_examples.rb +0 -13
  38. data/spec/async/clock_spec.rb +0 -37
  39. data/spec/async/condition_examples.rb +0 -105
  40. data/spec/async/condition_spec.rb +0 -72
  41. data/spec/async/logger_spec.rb +0 -65
  42. data/spec/async/node_spec.rb +0 -193
  43. data/spec/async/notification_spec.rb +0 -66
  44. data/spec/async/performance_spec.rb +0 -72
  45. data/spec/async/queue_spec.rb +0 -133
  46. data/spec/async/reactor/nested_spec.rb +0 -52
  47. data/spec/async/reactor_spec.rb +0 -253
  48. data/spec/async/semaphore_spec.rb +0 -169
  49. data/spec/async/task_spec.rb +0 -476
  50. data/spec/async/wrapper_spec.rb +0 -203
  51. data/spec/async_spec.rb +0 -33
  52. data/spec/enumerator_spec.rb +0 -83
  53. data/spec/kernel/async_spec.rb +0 -39
  54. data/spec/kernel/sync_spec.rb +0 -54
  55. data/spec/spec_helper.rb +0 -18
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- threads = []
4
-
5
- (1..).each do |i|
6
- threads << Thread.new{sleep}
7
- puts i
8
- end
9
-
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
23
-
24
- require 'benchmark/ips'
25
-
26
- GC.disable
27
-
28
- Benchmark.ips do |benchmark|
29
- benchmark.time = 1
30
- benchmark.warmup = 1
31
-
32
- benchmark.report("Thread.new{}") do |count|
33
- while count > 0
34
- Thread.new{count -= 1}.join
35
- end
36
- end
37
-
38
- benchmark.report("Fiber.new{}") do |count|
39
- while count > 0
40
- Fiber.new{count -= 1}.resume
41
- end
42
- end
43
-
44
- benchmark.compare!
45
- end
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require_relative '../lib/async'
5
-
6
- module Async::Methods
7
- def sleep(*args)
8
- Async::Task.current.sleep(*args)
9
- end
10
-
11
- def async(name)
12
- original_method = self.method(name)
13
-
14
- define_method(name) do |*args|
15
- Async::Reactor.run do |task|
16
- original_method.call(*args)
17
- end
18
- end
19
- end
20
-
21
- def await(&block)
22
- block.call.wait
23
- end
24
-
25
- def barrier!
26
- Async::Task.current.children.each(&:wait)
27
- end
28
- end
29
-
30
- include Async::Methods
31
-
32
- async def count_chickens(area_name)
33
- 3.times do |i|
34
- sleep rand
35
-
36
- puts "Found a chicken in the #{area_name}!"
37
- end
38
- end
39
-
40
- async def find_chicken(areas)
41
- puts "Searching for chicken..."
42
-
43
- sleep rand * 5
44
-
45
- return areas.sample
46
- end
47
-
48
- async def count_all_chckens
49
- # These methods all run at the same time.
50
- count_chickens("garden")
51
- count_chickens("house")
52
- count_chickens("tree")
53
-
54
- # Wait for all previous async work to complete...
55
- barrier!
56
-
57
- puts "There was a chicken in the #{find_chicken(["garden", "house", "tree"]).wait}"
58
- end
59
-
60
- count_all_chckens
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'async/reactor'
4
-
5
- class Callback
6
- def initialize
7
- @reactor = Async::Reactor.new
8
- end
9
-
10
- def close
11
- @reactor.close
12
- end
13
-
14
- # If duration is 0, it will happen immediately after the task is started.
15
- def run(duration = 0)
16
- @reactor.run do |task|
17
- @reactor.after(duration) do
18
- @reactor.stop
19
- end
20
-
21
- yield(task) if block_given?
22
- end
23
- end
24
- end
25
-
26
-
27
- callback = Callback.new
28
-
29
- begin
30
- callback.run do |task|
31
- while true
32
- task.sleep(2)
33
- puts "Hello from task!"
34
- end
35
- end
36
-
37
- while true
38
- callback.run(0)
39
- puts "Sleeping for 1 second"
40
- sleep(1)
41
- end
42
- ensure
43
- callback.close
44
- end
@@ -1,59 +0,0 @@
1
- # Capture
2
-
3
- ## Falcon
4
-
5
- ```
6
- % wrk -t 8 -c 32 http://localhost:9292/
7
- Running 10s test @ http://localhost:9292/
8
- 8 threads and 32 connections
9
- Thread Stats Avg Stdev Max +/- Stdev
10
- Latency 106.31ms 10.20ms 211.79ms 98.00%
11
- Req/Sec 37.94 5.43 40.00 84.24%
12
- 3003 requests in 10.01s, 170.16KB read
13
- Requests/sec: 299.98
14
- Transfer/sec: 17.00KB
15
- ```
16
-
17
- ```
18
- 0.0s: Process 28065 start times:
19
- | #<struct Process::Tms utime=2.38, stime=0.0, cutime=0.0, cstime=0.2>
20
- ^C15.11s: strace -p 28065
21
- | ["sendto", {:"% time"=>57.34, :seconds=>0.595047, :"usecs/call"=>14, :calls=>39716, :errors=>32, :syscall=>"sendto"}]
22
- | ["recvfrom", {:"% time"=>42.58, :seconds=>0.441867, :"usecs/call"=>12, :calls=>36718, :errors=>70, :syscall=>"recvfrom"}]
23
- | ["read", {:"% time"=>0.07, :seconds=>0.000723, :"usecs/call"=>7, :calls=>98, :errors=>nil, :syscall=>"read"}]
24
- | ["write", {:"% time"=>0.01, :seconds=>0.000112, :"usecs/call"=>56, :calls=>2, :errors=>nil, :syscall=>"write"}]
25
- | [:total, {:"% time"=>100.0, :seconds=>1.037749, :"usecs/call"=>nil, :calls=>76534, :errors=>102, :syscall=>"total"}]
26
- 15.11s: Process 28065 end times:
27
- | #<struct Process::Tms utime=3.93, stime=0.0, cutime=0.0, cstime=0.2>
28
- 15.11s: Process Waiting: 1.0377s out of 1.55s
29
- | Wait percentage: 66.95%
30
- ```
31
-
32
- ## Puma
33
-
34
- ```
35
- wrk -t 8 -c 32 http://localhost:9292/
36
- Running 10s test @ http://localhost:9292/
37
- 8 threads and 32 connections
38
- Thread Stats Avg Stdev Max +/- Stdev
39
- Latency 108.83ms 3.50ms 146.38ms 86.58%
40
- Req/Sec 34.43 6.70 40.00 92.68%
41
- 1371 requests in 10.01s, 81.67KB read
42
- Requests/sec: 136.94
43
- Transfer/sec: 8.16KB
44
- ```
45
-
46
- ```
47
- 0.0s: Process 28448 start times:
48
- | #<struct Process::Tms utime=0.63, stime=0.0, cutime=0.0, cstime=0.2>
49
- ^C24.89s: strace -p 28448
50
- | ["recvfrom", {:"% time"=>64.65, :seconds=>0.595275, :"usecs/call"=>13, :calls=>44476, :errors=>27769, :syscall=>"recvfrom"}]
51
- | ["sendto", {:"% time"=>30.68, :seconds=>0.282467, :"usecs/call"=>18, :calls=>15288, :errors=>nil, :syscall=>"sendto"}]
52
- | ["write", {:"% time"=>4.66, :seconds=>0.042921, :"usecs/call"=>15, :calls=>2772, :errors=>nil, :syscall=>"write"}]
53
- | ["read", {:"% time"=>0.02, :seconds=>0.000157, :"usecs/call"=>8, :calls=>19, :errors=>1, :syscall=>"read"}]
54
- | [:total, {:"% time"=>100.0, :seconds=>0.92082, :"usecs/call"=>nil, :calls=>62555, :errors=>27770, :syscall=>"total"}]
55
- 24.89s: Process 28448 end times:
56
- | #<struct Process::Tms utime=3.19, stime=0.0, cutime=0.0, cstime=0.2>
57
- 24.89s: Process Waiting: 0.9208s out of 2.56s
58
- | Wait percentage: 35.97%
59
- ```
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'irb'
5
- require 'console'
6
-
7
- pids = ARGV.collect(&:to_i)
8
-
9
- TICKS = Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz).to_f
10
-
11
- def getrusage(pid)
12
- fields = File.read("/proc/#{pid}/stat").split(/\s+/)
13
-
14
- return Process::Tms.new(
15
- fields[14].to_f / TICKS,
16
- fields[15].to_f / TICKS,
17
- fields[16].to_f / TICKS,
18
- fields[17].to_f / TICKS,
19
- )
20
- end
21
-
22
- def parse(value)
23
- case value
24
- when /^\s*\d+\.\d+/
25
- Float(value)
26
- when /^\s*\d+/
27
- Integer(value)
28
- else
29
- value = value.strip
30
- if value.empty?
31
- nil
32
- else
33
- value
34
- end
35
- end
36
- end
37
-
38
- def strace(pid, duration = 60)
39
- input, output = IO.pipe
40
-
41
- pid = Process.spawn("strace", "-p", pid.to_s, "-cqf", "-w", "-e", "!futex", err: output)
42
-
43
- output.close
44
-
45
- Signal.trap(:INT) do
46
- Process.kill(:INT, pid)
47
- Signal.trap(:INT, :DEFAULT)
48
- end
49
-
50
- Thread.new do
51
- sleep duration
52
- Process.kill(:INT, pid)
53
- end
54
-
55
- summary = {}
56
-
57
- if first_line = input.gets
58
- if rule = input.gets # horizontal separator
59
- pattern = Regexp.new(
60
- rule.split(/\s/).map{|s| "(.{1,#{s.size}})"}.join(' ')
61
- )
62
-
63
- header = pattern.match(first_line).captures.map{|key| key.strip.to_sym}
64
- end
65
-
66
- while line = input.gets
67
- break if line == rule
68
- row = pattern.match(line).captures.map{|value| parse(value)}
69
- fields = header.zip(row).to_h
70
-
71
- summary[fields[:syscall]] = fields
72
- end
73
-
74
- if line = input.gets
75
- row = pattern.match(line).captures.map{|value| parse(value)}
76
- fields = header.zip(row).to_h
77
- summary[:total] = fields
78
- end
79
- end
80
-
81
- _, status = Process.waitpid2(pid)
82
-
83
- Console.logger.error(status) do |buffer|
84
- buffer.puts first_line
85
- end unless status.success?
86
-
87
- return summary
88
- end
89
-
90
- pids.each do |pid|
91
- start_times = getrusage(pid)
92
- Console.logger.info("Process #{pid} start times:", start_times)
93
-
94
- # sleep 60
95
- summary = strace(pid)
96
-
97
- Console.logger.info("strace -p #{pid}") do |buffer|
98
- summary.each do |fields|
99
- buffer.puts fields.inspect
100
- end
101
- end
102
-
103
- end_times = getrusage(pid)
104
- Console.logger.info("Process #{pid} end times:", end_times)
105
-
106
- if total = summary[:total]
107
- process_duration = end_times.utime - start_times.utime
108
- wait_duration = summary[:total][:seconds]
109
-
110
- Console.logger.info("Process Waiting: #{wait_duration.round(4)}s out of #{process_duration.round(4)}s") do |buffer|
111
- buffer.puts "Wait percentage: #{(wait_duration / process_duration * 100.0).round(2)}%"
112
- end
113
- else
114
- Console.logger.warn("No system calls detected.")
115
- end
116
- end
@@ -1,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fiber'
4
-
5
- class IO
6
- READABLE = 1
7
- WRITABLE = 2
8
-
9
- # rb_wait_for_single_fd (int fd, int events, struct timeval *tv)
10
- def self.wait(descriptor, events, duration)
11
- fiber = Fiber.current
12
- reactor = fiber.reactor
13
-
14
- monitor = reactor.add_io(fiber, descriptor, state)
15
-
16
- fiber.with_timeout(duration) do
17
- result = Fiber.yield
18
- raise result if result.is_a? Exception
19
- end
20
-
21
- return result
22
- ensure
23
- reactor.remove_io(monitor)
24
- end
25
-
26
- def wait_readable(duration = nil)
27
- wait_any(READABLE)
28
- end
29
-
30
- def wait_writable(duration = nil)
31
- wait_any(WRITABLE)
32
- end
33
-
34
- def wait_until(events = READABLE|WRITABLE, duration = nil)
35
- IO.wait_for_io(self.fileno, events, duration)
36
- end
37
- end
38
-
39
- class Fiber
40
- # Raised when a task times out.
41
- class TimeoutError < RuntimeError
42
- end
43
-
44
- # This should be inherited by nested fibers.
45
- attr :reactor
46
-
47
- def timeout(duration)
48
- reactor = self.reactor
49
- backtrace = caller
50
-
51
- timer = reactor.add_timer(duration) do
52
- if self.alive?
53
- error = Fiber::TimeoutError.new("execution expired")
54
- error.set_backtrace backtrace
55
- self.resume error
56
- end
57
- end
58
-
59
- yield
60
- ensure
61
- reactor.cancel_timer(timer)
62
- end
63
- end
64
-
65
- # Can be standard implementation, but could also be provided by external gem/library.
66
- class Fiber::Reactor
67
- # Add IO to the reactor. The reactor will call `fiber.resume` when the event is triggered.
68
- # Returns an opaque monitor object which can be passed to `remove_io` to stop waiting for events.
69
- def add_io(fiber, io, state)
70
- # The motivation for add_io and remove_io is that it's how a lot of the underlying APIs work, where remove_io just takes the file descriptor.
71
- # It also avoids the need for any memory allocation, and maps well to how it's typically used (i.e. in an implementation of `IO#read`).
72
- # An efficient implementation might do it's job and then just:
73
- return io
74
- end
75
-
76
- def remove_io(monitor)
77
- end
78
-
79
- # The reactor will call the block at some point after duration time has elapsed.
80
- # Returns an opaque timer object which can be passed to `cancel_timer` to avoid this happening.
81
- def add_timer(duration, &block)
82
- end
83
-
84
- def cancel_timer(timer)
85
- end
86
-
87
- # Run until idle (no registered io/timers), or duration time has passed if specified.
88
- def run(duration = nil)
89
- end
90
-
91
- # Stop the reactor as soon as possible. Can be called from another thread.
92
- def stop
93
- end
94
-
95
- def close
96
- # Close the reactor so it can no longer be used.
97
- end
98
- end
99
-
100
- # Basic non-blocking task:
101
- reactor = Fiber::Reactor.new
102
-
103
- # User could provide their own reactor, it might even do other things, but the basic interface above should continue to work.
104
-
105
- Fiber.new(reactor: reactor) do
106
- # Blocking operations call Fiber.yield, which goes to...
107
- end.resume
108
-
109
- # ...here, which starts running the reactor which can also be controlled (e.g. duration, stopping)
110
- reactor.run
111
-
112
- # Here is a rough outline of the reactor concept implementation using NIO4R
113
- # Can be standard implementation, but could also be provided by external gem/library.
114
- class NIO::Reactor
115
- def initialize
116
- @selector = NIO::Selector.new
117
- @timers = Timers::Group.new
118
-
119
- @stopped = true
120
- end
121
-
122
- EVENTS = [
123
- :r,
124
- :w,
125
- :rw
126
- ]
127
-
128
- def add_io(fiber, io, event)
129
- monitor = @selector.register(io, EVENTS[event])
130
- monitor.value = fiber
131
- end
132
-
133
- def remove_io(monitor)
134
- monitor.cancel
135
- end
136
-
137
- # The reactor will call `fiber.resume(Fiber::TimeoutError)` at some point after duration time has elapsed.
138
- # Returns an opaque timer object which can be passed to `cancel_timer` to avoid this happening.
139
- def add_timer(fiber, duration)
140
- @timers.after(duration, &block)
141
- end
142
-
143
- def cancel_timer(timer)
144
- timer.cancel
145
- end
146
-
147
- # Run until idle (no registered io/timers), or duration time has passed if specified.
148
- def run(duration = nil)
149
- @timers.wait do |interval|
150
- # - nil: no timers
151
- # - -ve: timers expired already
152
- # - 0: timers ready to fire
153
- # - +ve: timers waiting to fire
154
- interval = 0 if interval && interval < 0
155
-
156
- # If there is nothing to do, then finish:
157
- return if @fibers.empty? && interval.nil?
158
-
159
- if monitors = @selector.select(interval)
160
- monitors.each do |monitor|
161
- if fiber = monitor.value
162
- fiber.resume
163
- end
164
- end
165
- end
166
- end until @stopped
167
- end
168
-
169
- def stop
170
- @stopped = true
171
- @selector.wakeup
172
- end
173
-
174
- def close
175
- @seletor.close
176
- end
177
- end
178
-