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