async 1.3.0 → 1.4.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: c6eb0c5b8b5f8a6b2a05ce7c6a563d7deacb8a3ce0da0f0a7797ecc8080790b4
4
- data.tar.gz: b0028ef539d8d11026bad3cc476b4fc2af5122e1ac5485888c456b9894be2084
3
+ metadata.gz: c4f42d14b27e2c7835cd745cae1e3fed8cea3ab36e4e919676b38673fc8adf32
4
+ data.tar.gz: 8944fd0464f0f1ef8135440bf4caa5a0235a0ef03de5e115609794c2a880ef76
5
5
  SHA512:
6
- metadata.gz: bb55a40ee7d6de6c8ed5d32af4ce29c2e8e8395d9cdec05c6a4d8328cb5fe6794e30d743132967a7b5c985b312a8e0ae5daee54ff6766912f2ebfc218f1119ce
7
- data.tar.gz: 9b767f9dff4254c65377ffcef840082a3e31dce20d8abdfe85c0aed0e7e3ddce7819fd30eaec0623e7428e51528694dc9208597fc416b79f3b9146357d9a72f9
6
+ metadata.gz: 3843d40191f22c646adfd596bb0360431dcca7152127fb90154932b430d2cbcee4e6a6479b804b34ebceef0b92535bf46d7c856a0c3aed6c3b9843c5973c2a09
7
+ data.tar.gz: cc595c9163bf0819bdb5f2029685bc4839faa569f88cb6cb969662d89732f2a0c25da0f7b78f297eb061e4c54d68e50ae6a5ed079a13a781f846cfee47edcea6
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in utopia.gemspec
4
3
  gemspec
5
4
 
6
5
  group :development do
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:test)
5
5
 
6
- task :default => [:test, :external]
6
+ task :default => :test
7
7
 
8
8
  def clone_and_test(name)
9
9
  sh("git clone https://git@github.com/socketry/#{name}")
@@ -5,39 +5,78 @@ require 'lightio'
5
5
 
6
6
  require 'benchmark/ips'
7
7
 
8
- def run_async(count = 10000)
8
+ #
9
+ # It's hard to know exactly how to interpret these results. When running parallel
10
+ # instances, resource contention is more likely to be a problem, and yet with
11
+ # async, the performance between a single task and several tasks is roughly the
12
+ # same, while in the case of lightio, there is an obvious performance gap.
13
+ #
14
+ # The main takeaway is that contention causes issues and if systems are not
15
+ # designed with that in mind, it will impact performance.
16
+ #
17
+ # $ ruby async_vs_lightio.rb
18
+ # Warming up --------------------------------------
19
+ # lightio (synchronous)
20
+ # 2.439k i/100ms
21
+ # async (synchronous) 2.115k i/100ms
22
+ # lightio (parallel) 211.000 i/100ms
23
+ # async (parallel) 449.000 i/100ms
24
+ # Calculating -------------------------------------
25
+ # lightio (synchronous)
26
+ # 64.502k (± 3.9%) i/s - 643.896k in 10.002151s
27
+ # async (synchronous) 161.195k (± 1.6%) i/s - 1.612M in 10.000976s
28
+ # lightio (parallel) 49.827k (±17.5%) i/s - 477.704k in 9.999579s
29
+ # async (parallel) 166.862k (± 6.2%) i/s - 1.662M in 10.000365s
30
+ #
31
+ # Comparison:
32
+ # async (parallel): 166862.3 i/s
33
+ # async (synchronous): 161194.6 i/s - same-ish: difference falls within error
34
+ # lightio (synchronous): 64502.5 i/s - 2.59x slower
35
+ # lightio (parallel): 49827.3 i/s - 3.35x slower
36
+
37
+
38
+ DURATION = 0.000001
39
+
40
+ def run_async(count, repeats = 10000)
9
41
  Async::Reactor.run do |task|
10
- tasks = count.times.map do
11
- # LightIO::Beam is a thread-like executor, use it instead Thread
42
+ count.times.map do
12
43
  task.async do |subtask|
13
- # do some io operations in beam
14
- subtask.sleep(0.0001)
44
+ repeats.times do
45
+ subtask.sleep(DURATION)
46
+ end
15
47
  end
16
- end
17
-
18
- tasks.each(&:wait)
48
+ end.each(&:wait)
19
49
  end
20
50
  end
21
51
 
22
- def run_lightio(count = 10000)
23
- beams = count.times.map do
24
- # LightIO::Beam is a thread-like executor, use it instead Thread
52
+ def run_lightio(count, repeats = 10000)
53
+ count.times.map do
25
54
  LightIO::Beam.new do
26
- # do some io operations in beam
27
- LightIO.sleep(0.0001)
55
+ repeats.times do
56
+ LightIO.sleep(DURATION)
57
+ end
28
58
  end
29
- end
30
-
31
- beams.each(&:join)
59
+ end.each(&:join)
32
60
  end
33
61
 
34
62
  Benchmark.ips do |benchmark|
35
- benchmark.report("lightio") do |count|
36
- run_lightio(count)
63
+ benchmark.time = 10
64
+ benchmark.warmup = 2
65
+
66
+ benchmark.report("lightio (synchronous)") do |count|
67
+ run_lightio(1, count)
68
+ end
69
+
70
+ benchmark.report("async (synchronous)") do |count|
71
+ run_async(1, count)
72
+ end
73
+
74
+ benchmark.report("lightio (parallel)") do |count|
75
+ run_lightio(32, count/32)
37
76
  end
38
77
 
39
- benchmark.report("async") do |count|
40
- run_async(count)
78
+ benchmark.report("async (parallel)") do |count|
79
+ run_async(32, count/32)
41
80
  end
42
81
 
43
82
  benchmark.compare!
@@ -0,0 +1,178 @@
1
+
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.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
+
@@ -23,7 +23,7 @@ require 'forwardable'
23
23
  require_relative 'node'
24
24
 
25
25
  module Async
26
- # A synchronization primative, which allows fibers to wait until a particular condition is triggered.
26
+ # A synchronization primative, which allows fibers to wait until a particular condition is triggered. Signalling the condition directly resumes the waiting fibers and thus blocks the caller.
27
27
  class Condition
28
28
  def initialize
29
29
  @waiting = []
@@ -37,17 +37,49 @@ module Async
37
37
  Task.yield
38
38
  end
39
39
 
40
+ # Is any fiber waiting on this notification?
41
+ # @return [Boolean]
42
+ def empty?
43
+ @waiting.empty?
44
+ end
45
+
40
46
  # Signal to a given task that it should resume operations.
41
47
  # @param value The value to return to the waiting fibers.
42
48
  # @see Task.yield which is responsible for handling value.
43
49
  # @return [void]
44
50
  def signal(value = nil)
45
- # TODO: Should we hot-swap @waiting - so that tasks can wait on this condition again?
46
- while task = @waiting.pop
47
- task.resume(value)
51
+ waiting = @waiting
52
+ @waiting = []
53
+
54
+ waiting.each do |fiber|
55
+ fiber.resume(value) if fiber.alive?
48
56
  end
49
57
 
50
58
  return nil
51
59
  end
60
+
61
+ # Signal to a given task that it should resume operations.
62
+ # @return [void]
63
+ def resume(value = nil, task: Task.current)
64
+ return if @waiting.empty?
65
+
66
+ task.reactor << Signal.new(@waiting, value)
67
+
68
+ @waiting = []
69
+
70
+ return nil
71
+ end
72
+
73
+ Signal = Struct.new(:waiting, :value) do
74
+ def alive?
75
+ true
76
+ end
77
+
78
+ def resume
79
+ waiting.each do |fiber|
80
+ fiber.resume(value) if fiber.alive?
81
+ end
82
+ end
83
+ end
52
84
  end
53
85
  end
@@ -0,0 +1,50 @@
1
+ # Copyright, 2017, 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 'condition'
22
+
23
+ module Async
24
+ # A synchronization primative, which allows fibers to wait until a notification is received. Does not block the task which signals the notification. Waiting tasks are resumed on next iteration of the reactor.
25
+ class Notification < Condition
26
+ # Signal to a given task that it should resume operations.
27
+ # @return [void]
28
+ def signal(value = nil, task: Task.current)
29
+ return if @waiting.empty?
30
+
31
+ task.reactor << Signal.new(@waiting, value)
32
+
33
+ @waiting = []
34
+
35
+ return nil
36
+ end
37
+
38
+ Signal = Struct.new(:waiting, :value) do
39
+ def alive?
40
+ true
41
+ end
42
+
43
+ def resume
44
+ waiting.each do |fiber|
45
+ fiber.resume(value) if fiber.alive?
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -64,6 +64,8 @@ module Async
64
64
  @selector = NIO::Selector.new
65
65
  @timers = Timers::Group.new
66
66
 
67
+ @ready = []
68
+
67
69
  @stopped = true
68
70
  end
69
71
 
@@ -116,7 +118,19 @@ module Async
116
118
  @selector.wakeup
117
119
  end
118
120
  end
119
-
121
+
122
+ # Schedule a fiber (or equivalent object) to be resumed on the next loop through the reactor.
123
+ def << fiber
124
+ @ready << fiber
125
+ end
126
+
127
+ # Yield the current fiber and resume it on the next iteration of the event loop.
128
+ def yield(fiber = Fiber.current)
129
+ @ready << fiber
130
+
131
+ Fiber.yield
132
+ end
133
+
120
134
  # Run the reactor until either all tasks complete or {#stop} is invoked.
121
135
  # Proxies arguments to {#async} immediately before entering the loop.
122
136
  def run(*args, &block)
@@ -137,6 +151,10 @@ module Async
137
151
  # Async.logger.debug{"[#{self} Pre] Updating #{@children.count} children..."}
138
152
  # As timeouts may have been updated, and caused fibers to complete, we should check this.
139
153
 
154
+ @ready.each do |fiber|
155
+ fiber.resume if fiber.alive?
156
+ end; @ready.clear
157
+
140
158
  # If there is nothing to do, then finish:
141
159
  # Async.logger.debug{"[#{self}] @children.empty? = #{@children.empty?} && interval #{interval.inspect}"}
142
160
  return initial_task if @children.empty? && interval.nil?
@@ -178,11 +196,11 @@ module Async
178
196
  # Put the calling fiber to sleep for a given ammount of time.
179
197
  # @param duration [Numeric] The time in seconds, to sleep for.
180
198
  def sleep(duration)
181
- task = Fiber.current
199
+ fiber = Fiber.current
182
200
 
183
201
  timer = self.after(duration) do
184
- if task.alive?
185
- task.resume
202
+ if fiber.alive?
203
+ fiber.resume
186
204
  end
187
205
  end
188
206
 
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "1.3.0"
22
+ VERSION = "1.4.0"
23
23
  end
@@ -0,0 +1,53 @@
1
+ # Copyright, 2018, 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
+ RSpec.shared_examples Async::Condition do
22
+ it 'can signal waiting task' do
23
+ state = nil
24
+
25
+ task = reactor.async do
26
+ state = :waiting
27
+ subject.wait
28
+ state = :resumed
29
+ end
30
+
31
+ expect(state).to be == :waiting
32
+
33
+ subject.signal
34
+
35
+ reactor.yield
36
+
37
+ expect(state).to be == :resumed
38
+ end
39
+
40
+ it 'should be able to signal stopped task' do
41
+ expect(subject.empty?).to be_truthy
42
+
43
+ task = reactor.async do
44
+ subject.wait
45
+ end
46
+
47
+ expect(subject.empty?).to be_falsey
48
+
49
+ task.stop
50
+
51
+ subject.signal
52
+ end
53
+ end
@@ -20,6 +20,8 @@
20
20
 
21
21
  require 'async/condition'
22
22
 
23
+ require_relative 'condition_examples'
24
+
23
25
  RSpec.describe Async::Condition do
24
26
  include_context Async::RSpec::Reactor
25
27
 
@@ -38,4 +40,6 @@ RSpec.describe Async::Condition do
38
40
 
39
41
  task.stop
40
42
  end
43
+
44
+ it_behaves_like Async::Condition
41
45
  end
@@ -0,0 +1,63 @@
1
+ # Copyright, 2017, 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/notification'
22
+
23
+ require_relative 'condition_examples'
24
+
25
+ RSpec.describe Async::Notification do
26
+ include_context Async::RSpec::Reactor
27
+
28
+ it 'should continue after notification is signalled' do
29
+ sequence = []
30
+
31
+ task = reactor.async do
32
+ sequence << :waiting
33
+ subject.wait
34
+ sequence << :resumed
35
+ end
36
+
37
+ expect(task.status).to be :running
38
+
39
+ sequence << :running
40
+ # This will cause the task to exit:
41
+ subject.signal
42
+ sequence << :signalled
43
+
44
+ expect(task.status).to be :running
45
+
46
+ sequence << :yielding
47
+ reactor.yield
48
+ sequence << :finished
49
+
50
+ expect(task.status).to be :complete
51
+
52
+ expect(sequence).to be == [
53
+ :waiting,
54
+ :running,
55
+ :signalled,
56
+ :yielding,
57
+ :resumed,
58
+ :finished
59
+ ]
60
+ end
61
+
62
+ it_behaves_like Async::Condition
63
+ 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.3.0
4
+ version: 1.4.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: 2018-03-16 00:00:00.000000000 Z
11
+ date: 2018-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -115,12 +115,14 @@ files:
115
115
  - async.gemspec
116
116
  - benchmark/async_vs_lightio.rb
117
117
  - examples/async_method.rb
118
+ - examples/fibers.rb
118
119
  - examples/sleep_sort.rb
119
120
  - lib/async.rb
120
121
  - lib/async/condition.rb
121
122
  - lib/async/logger.rb
122
123
  - lib/async/measure.rb
123
124
  - lib/async/node.rb
125
+ - lib/async/notification.rb
124
126
  - lib/async/reactor.rb
125
127
  - lib/async/task.rb
126
128
  - lib/async/version.rb
@@ -129,8 +131,10 @@ files:
129
131
  - logo.svg
130
132
  - papers/1982 Grossman.pdf
131
133
  - papers/1987 ODell.pdf
134
+ - spec/async/condition_examples.rb
132
135
  - spec/async/condition_spec.rb
133
136
  - spec/async/node_spec.rb
137
+ - spec/async/notification_spec.rb
134
138
  - spec/async/performance_spec.rb
135
139
  - spec/async/reactor/nested_spec.rb
136
140
  - spec/async/reactor_spec.rb
@@ -162,8 +166,10 @@ signing_key:
162
166
  specification_version: 4
163
167
  summary: Async is an asynchronous I/O framework based on nio4r.
164
168
  test_files:
169
+ - spec/async/condition_examples.rb
165
170
  - spec/async/condition_spec.rb
166
171
  - spec/async/node_spec.rb
172
+ - spec/async/notification_spec.rb
167
173
  - spec/async/performance_spec.rb
168
174
  - spec/async/reactor/nested_spec.rb
169
175
  - spec/async/reactor_spec.rb