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 +4 -4
- data/Gemfile +0 -1
- data/Rakefile +1 -1
- data/benchmark/async_vs_lightio.rb +59 -20
- data/examples/fibers.rb +178 -0
- data/lib/async/condition.rb +36 -4
- data/lib/async/notification.rb +50 -0
- data/lib/async/reactor.rb +22 -4
- data/lib/async/version.rb +1 -1
- data/spec/async/condition_examples.rb +53 -0
- data/spec/async/condition_spec.rb +4 -0
- data/spec/async/notification_spec.rb +63 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4f42d14b27e2c7835cd745cae1e3fed8cea3ab36e4e919676b38673fc8adf32
|
4
|
+
data.tar.gz: 8944fd0464f0f1ef8135440bf4caa5a0235a0ef03de5e115609794c2a880ef76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3843d40191f22c646adfd596bb0360431dcca7152127fb90154932b430d2cbcee4e6a6479b804b34ebceef0b92535bf46d7c856a0c3aed6c3b9843c5973c2a09
|
7
|
+
data.tar.gz: cc595c9163bf0819bdb5f2029685bc4839faa569f88cb6cb969662d89732f2a0c25da0f7b78f297eb061e4c54d68e50ae6a5ed079a13a781f846cfee47edcea6
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -5,39 +5,78 @@ require 'lightio'
|
|
5
5
|
|
6
6
|
require 'benchmark/ips'
|
7
7
|
|
8
|
-
|
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
|
-
|
11
|
-
# LightIO::Beam is a thread-like executor, use it instead Thread
|
42
|
+
count.times.map do
|
12
43
|
task.async do |subtask|
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
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.
|
36
|
-
|
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!
|
data/examples/fibers.rb
ADDED
@@ -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
|
+
|
data/lib/async/condition.rb
CHANGED
@@ -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
|
-
|
46
|
-
|
47
|
-
|
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
|
data/lib/async/reactor.rb
CHANGED
@@ -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
|
-
|
199
|
+
fiber = Fiber.current
|
182
200
|
|
183
201
|
timer = self.after(duration) do
|
184
|
-
if
|
185
|
-
|
202
|
+
if fiber.alive?
|
203
|
+
fiber.resume
|
186
204
|
end
|
187
205
|
end
|
188
206
|
|
data/lib/async/version.rb
CHANGED
@@ -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.
|
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-
|
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
|