async 1.21.0 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9c9a081ed81fc6b4b0b34ebb5282063574994045189a9544b968c7729543cf1
4
- data.tar.gz: df9d9266a02ff86895cfc9934fbafbeeb7159cb1b964f10705323d9f4798ae34
3
+ metadata.gz: 4fa7e7ae29e8bff86757e97d1f63bb5fd297caa79f97b5e4ccdc1f4568d65609
4
+ data.tar.gz: 17239cfec1b3b5062fdf6745b7708ebbdd9f39c63199dc33cc6495362da016ca
5
5
  SHA512:
6
- metadata.gz: 1f5014904c26006fd50462c9161f3ae24f867ed995569bc1faecebe67d3a1b8799eb4a91896121a07885512ae21361be18f9f9ea3614825140cb3596239cc13b
7
- data.tar.gz: 26c13f37945ceb4d2542a2ef0736b6fe04cc9515d90053d4b2a00888a5501c008ce614849462b006216a94a1eece3fc781cf378f25f369ae65bceeede12dbfe6
6
+ metadata.gz: 18566e7d62eca30c72491cc06f633baa1f2b027fbb474f7bb226d0c5ea09cc095e06b3f2943470f378d98a2174ff8e0b4d7875643f3abba751fe69c76e3f6657
7
+ data.tar.gz: 12aed7ad107c756e90292a2179336441205d66a4f457dcf1d55e5655d9bc361753a9b4deb48c644104ca5d349610a1eb33773e995c91a7e2b92a71c97df3a0cd
@@ -0,0 +1,51 @@
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>
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'fiber'
5
+
6
+ puts
7
+ puts RUBY_DESCRIPTION
8
+
9
+ if RUBY_VERSION < "2.0"
10
+ class String
11
+ def b
12
+ self
13
+ end
14
+ end
15
+ end
16
+
17
+ # TODO: make these much larger, see if we're effectively batching
18
+ # even if we don't mean to...
19
+ QUERY_TEXT = "STATUS".freeze
20
+ RESPONSE_TEXT = "OK".freeze
21
+
22
+ NUM_WORKERS = (ARGV[0] || 10_000).to_i
23
+ NUM_REQUESTS = (ARGV[1] || 100).to_i
24
+
25
+ # Fiber reactor code taken from
26
+ # https://www.codeotaku.com/journal/2018-11/fibers-are-the-right-solution/index
27
+ class Reactor
28
+ def initialize
29
+ @readable = {}
30
+ @writable = {}
31
+ end
32
+
33
+ def run
34
+ while @readable.any? or @writable.any?
35
+ readable, writable = IO.select(@readable.keys, @writable.keys, [])
36
+
37
+ readable.each do |io|
38
+ @readable[io].resume
39
+ end
40
+
41
+ writable.each do |io|
42
+ @writable[io].resume
43
+ end
44
+ end
45
+ end
46
+
47
+ def wait_readable(io)
48
+ @readable[io] = Fiber.current
49
+ Fiber.yield
50
+ @readable.delete(io)
51
+ end
52
+
53
+ def wait_writable(io)
54
+ @writable[io] = Fiber.current
55
+ Fiber.yield
56
+ @writable.delete(io)
57
+ end
58
+ end
59
+
60
+ class Wrapper
61
+ def initialize(io, reactor)
62
+ @io = io
63
+ @reactor = reactor
64
+ end
65
+
66
+ if RUBY_VERSION >= "2.3"
67
+ def read_nonblock(length, buffer)
68
+ while true
69
+ case result = @io.read_nonblock(length, buffer, exception: false)
70
+ when :wait_readable
71
+ @reactor.wait_readable(@io)
72
+ when :wait_writable
73
+ @reactor.wait_writable(@io)
74
+ else
75
+ return result
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ def write_nonblock(buffer)
82
+ while true
83
+ case result = @io.write_nonblock(buffer, exception: false)
84
+ when :wait_readable
85
+ @reactor.wait_readable(@io)
86
+ when :wait_writable
87
+ @reactor.wait_writable(@io)
88
+ else
89
+ return result
90
+ end
91
+ end
92
+ end
93
+ else
94
+ def read_nonblock(length, buffer)
95
+ while true
96
+ begin
97
+ return @io.read_nonblock(length, buffer)
98
+ rescue IO::WaitReadable
99
+ @reactor.wait_readable(@io)
100
+ rescue IO::WaitWritable
101
+ @reactor.wait_writable(@io)
102
+ end
103
+ end
104
+ end
105
+
106
+ def write_nonblock(buffer)
107
+ while true
108
+ begin
109
+ return @io.write_nonblock(buffer)
110
+ rescue IO::WaitReadable
111
+ @reactor.wait_readable(@io)
112
+ rescue IO::WaitWritable
113
+ @reactor.wait_writable(@io)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def read(length, buffer = nil)
120
+ if buffer
121
+ buffer.clear
122
+ else
123
+ buffer = String.new.b
124
+ end
125
+
126
+ result = self.read_nonblock(length - buffer.bytesize, buffer)
127
+
128
+ if result == length
129
+ return result
130
+ end
131
+
132
+ chunk = String.new.b
133
+ while chunk = self.read_nonblock(length - buffer.bytesize, chunk)
134
+ buffer << chunk
135
+
136
+ break if buffer.bytesize == length
137
+ end
138
+
139
+ return buffer
140
+ end
141
+
142
+ def write(buffer)
143
+ remaining = buffer.dup
144
+
145
+ while true
146
+ result = self.write_nonblock(remaining)
147
+
148
+ if result == remaining.bytesize
149
+ return buffer.bytesize
150
+ else
151
+ remaining = remaining.byteslice(result, remaining.bytesize - result)
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ reactor = Reactor.new
158
+
159
+ worker_read = []
160
+ worker_write = []
161
+
162
+ master_read = []
163
+ master_write = []
164
+
165
+ workers = []
166
+
167
+ # puts "Setting up pipes..."
168
+ NUM_WORKERS.times do |i|
169
+ r, w = IO.pipe
170
+ worker_read.push Wrapper.new(r, reactor)
171
+ master_write.push Wrapper.new(w, reactor)
172
+
173
+ r, w = IO.pipe
174
+ worker_write.push Wrapper.new(w, reactor)
175
+ master_read.push Wrapper.new(r, reactor)
176
+ end
177
+
178
+ # puts "Setting up fibers..."
179
+ NUM_WORKERS.times do |i|
180
+ f = Fiber.new do
181
+ # Worker code
182
+ NUM_REQUESTS.times do |req_num|
183
+ q = worker_read[i].read(QUERY_TEXT.size)
184
+ if q != QUERY_TEXT
185
+ raise "Fail! Expected #{QUERY_TEXT.inspect} but got #{q.inspect} on request #{req_num.inspect}!"
186
+ end
187
+ worker_write[i].write(RESPONSE_TEXT)
188
+ end
189
+ end
190
+ workers.push f
191
+ end
192
+
193
+ workers.each { |f| f.resume }
194
+
195
+ master_fiber = Fiber.new do
196
+ NUM_WORKERS.times do |worker_num|
197
+ f = Fiber.new do
198
+ NUM_REQUESTS.times do |req_num|
199
+ master_write[worker_num].write(QUERY_TEXT)
200
+ buffer = master_read[worker_num].read(RESPONSE_TEXT.size)
201
+ if buffer != RESPONSE_TEXT
202
+ raise "Error! Fiber no. #{worker_num} on req #{req_num} expected #{RESPONSE_TEXT.inspect} but got #{buf.inspect}!"
203
+ end
204
+ end
205
+ end
206
+ f.resume
207
+ end
208
+ end
209
+
210
+ master_fiber.resume
211
+
212
+ # puts "Starting reactor..."
213
+ reactor.run
214
+
215
+ # puts "Done, finished all reactor Fibers!"
216
+
217
+ puts Process.times
218
+
219
+ # Exit
@@ -0,0 +1,51 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'task'
22
+
23
+ module Async
24
+ # A semaphore is used to control access to a common resource in a concurrent system. A useful way to think of a semaphore as used in the real-world systems is as a record of how many units of a particular resource are available, coupled with operations to adjust that record safely (i.e. to avoid race conditions) as units are required or become free, and, if necessary, wait until a unit of the resource becomes available.
25
+ class Barrier
26
+ def initialize
27
+ @tasks = []
28
+ end
29
+
30
+ # All tasks which have been invoked into the barrier.
31
+ attr :tasks
32
+
33
+ def async(*args, parent: Task.current, **options, &block)
34
+ task = parent.async(*args, **options, &block)
35
+
36
+ @tasks << task
37
+
38
+ return task
39
+ end
40
+
41
+ def empty?
42
+ @tasks.empty?
43
+ end
44
+
45
+ def wait
46
+ while task = @tasks.pop
47
+ task.wait
48
+ end
49
+ end
50
+ end
51
+ end
@@ -116,9 +116,9 @@ module Async
116
116
  # When calling an async block, we deterministically execute it until the
117
117
  # first blocking operation. We don't *have* to do this - we could schedule
118
118
  # it for later execution, but it's useful to:
119
- # - Fail at the point of call where possible.
119
+ # - Fail at the point of the method call where possible.
120
120
  # - Execute determinstically where possible.
121
- # - Avoid overhead if no blocking operation is performed.
121
+ # - Avoid scheduler overhead if no blocking operation is performed.
122
122
  task.run(*args)
123
123
 
124
124
  # logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
@@ -168,7 +168,7 @@ module Async
168
168
 
169
169
  initial_task = self.async(*args, &block) if block_given?
170
170
 
171
- @timers.wait do |interval|
171
+ until @stopped
172
172
  # logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
173
173
 
174
174
  if @ready.any?
@@ -180,17 +180,16 @@ module Async
180
180
  end
181
181
 
182
182
  @running.clear
183
-
184
- # if there are tasks ready to execute, don't sleep.
185
- if @ready.any?
186
- interval = 0
187
- else
188
- # The above tasks may schedule, cancel or affect timers in some way. We need to compute a new wait interval for the blocking selector call below:
189
- interval = @timers.wait_interval
190
- end
191
183
  end
192
184
 
193
- # As timeouts may have been updated, and caused fibers to complete, we should check this.
185
+ if @ready.empty?
186
+ interval = @timers.wait_interval
187
+ else
188
+ # if there are tasks ready to execute, don't sleep:
189
+ interval = 0
190
+ end
191
+
192
+ # If there is no interval to wait (thus no timers), and no tasks, we could be done:
194
193
  if interval.nil?
195
194
  if self.finished?
196
195
  # If there is nothing to do, then finish:
@@ -207,7 +206,9 @@ module Async
207
206
  monitor.value.resume
208
207
  end
209
208
  end
210
- end until @stopped
209
+
210
+ @timers.fire
211
+ end
211
212
 
212
213
  return initial_task
213
214
  ensure
@@ -47,12 +47,10 @@ module Async
47
47
  end
48
48
 
49
49
  # Run an async task. Will wait until the semaphore is ready until spawning and running the task.
50
- def async(*args)
51
- parent = Task.current
52
-
50
+ def async(*args, parent: Task.current, **options)
53
51
  wait
54
52
 
55
- parent.async do |task|
53
+ parent.async(**options) do |task|
56
54
  @count += 1
57
55
 
58
56
  begin
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "1.21.0"
22
+ VERSION = "1.22.0"
23
23
  end
@@ -0,0 +1,77 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/barrier'
22
+ require 'async/clock'
23
+ require 'async/rspec'
24
+
25
+ RSpec.describe Async::Barrier do
26
+ include_context Async::RSpec::Reactor
27
+
28
+ context '#async' do
29
+ let(:repeats) {40}
30
+ let(:delay) {0.1}
31
+
32
+ it 'should wait for all jobs to complete' do
33
+ finished = 0
34
+
35
+ repeats.times.map do |i|
36
+ subject.async do |task|
37
+ task.sleep(delay)
38
+ finished += 1
39
+
40
+ # This task is a child task but not part of the barrier.
41
+ task.async do
42
+ task.sleep(delay*3)
43
+ end
44
+ end
45
+ end
46
+
47
+ expect(subject).to_not be_empty
48
+ expect(finished).to be < repeats
49
+
50
+ duration = Async::Clock.measure{subject.wait}
51
+
52
+ expect(duration).to be < (delay * 2)
53
+ expect(finished).to be == repeats
54
+ expect(subject).to be_empty
55
+ end
56
+ end
57
+
58
+ context '#wait' do
59
+ it 'should wait for tasks even after exceptions' do
60
+ task1 = subject.async do
61
+ raise "Boom"
62
+ end
63
+
64
+ task2 = subject.async do
65
+ end
66
+
67
+ expect(task1).to be_failed
68
+ expect(task2).to be_finished
69
+
70
+ expect{subject.wait}.to raise_exception(/Boom/)
71
+
72
+ subject.wait until subject.empty?
73
+
74
+ expect(subject).to be_empty
75
+ end
76
+ end
77
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.21.0
4
+ version: 1.22.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: 2019-09-17 00:00:00.000000000 Z
11
+ date: 2019-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -144,6 +144,8 @@ files:
144
144
  - async.gemspec
145
145
  - benchmark/async_vs_lightio.rb
146
146
  - benchmark/fiber_count.rb
147
+ - benchmark/rubies/README.md
148
+ - benchmark/rubies/benchmark.rb
147
149
  - benchmark/thread_count.rb
148
150
  - benchmark/thread_vs_fiber.rb
149
151
  - examples/async_method.rb
@@ -157,6 +159,7 @@ files:
157
159
  - examples/stop/sleep.rb
158
160
  - gems/event.gemfile
159
161
  - lib/async.rb
162
+ - lib/async/barrier.rb
160
163
  - lib/async/clock.rb
161
164
  - lib/async/condition.rb
162
165
  - lib/async/debug/monitor.rb
@@ -176,6 +179,7 @@ files:
176
179
  - logo.svg
177
180
  - papers/1982 Grossman.pdf
178
181
  - papers/1987 ODell.pdf
182
+ - spec/async/barrier_spec.rb
179
183
  - spec/async/clock_spec.rb
180
184
  - spec/async/condition_examples.rb
181
185
  - spec/async/condition_spec.rb
@@ -218,6 +222,7 @@ signing_key:
218
222
  specification_version: 4
219
223
  summary: Async is an asynchronous I/O framework based on nio4r.
220
224
  test_files:
225
+ - spec/async/barrier_spec.rb
221
226
  - spec/async/clock_spec.rb
222
227
  - spec/async/condition_examples.rb
223
228
  - spec/async/condition_spec.rb