async 1.3.0 → 1.4.0

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 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