async 1.25.0 → 1.25.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/node.rb +170 -49
- data/lib/async/reactor.rb +12 -7
- data/lib/async/version.rb +1 -1
- data/spec/async/node_spec.rb +27 -9
- data/spec/async/queue_spec.rb +7 -7
- data/spec/async/reactor_spec.rb +22 -2
- data/spec/async/task_spec.rb +10 -0
- data/spec/spec_helper.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70efaa49b29fd36596f22f7796c475610955cc63836725b00775f6c1025aa976
|
4
|
+
data.tar.gz: d5e7987bcb581c20b3ac17fba0ef803a61f154ec107b55ad01220c16ae2a32e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d47987f2b355ab4dff7e06443787190dc8567abe30391b3a0a2dde55efb4592c14af94b42f52cf2b7f6e35d9868bb1f2cfbdd23cb9abddb4c1ba34a97cf0dab9
|
7
|
+
data.tar.gz: 014fa5e68d7fb509f6120cbce6e07f75d6c17c86b8d5dcc93278555c4e11ea27a2a8883ba8193359aeed1ab3d5303d92e30b053cf840aa31c0b4f9b002d7c5d4
|
data/lib/async/node.rb
CHANGED
@@ -20,49 +20,181 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
require 'set'
|
24
|
-
|
25
23
|
module Async
|
24
|
+
# A double linked list.
|
25
|
+
class List
|
26
|
+
def initialize
|
27
|
+
@head = nil
|
28
|
+
@tail = nil
|
29
|
+
@size = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :size
|
33
|
+
|
34
|
+
attr_accessor :head
|
35
|
+
attr_accessor :tail
|
36
|
+
|
37
|
+
# Inserts an item at the end of the list.
|
38
|
+
def insert(item)
|
39
|
+
unless @head
|
40
|
+
@head = item
|
41
|
+
@tail = item
|
42
|
+
|
43
|
+
# Consistency:
|
44
|
+
item.head = nil
|
45
|
+
item.tail = nil
|
46
|
+
else
|
47
|
+
@tail.tail = item
|
48
|
+
item.head = @tail
|
49
|
+
|
50
|
+
# Consistency:
|
51
|
+
item.tail = nil
|
52
|
+
|
53
|
+
@tail = item
|
54
|
+
end
|
55
|
+
|
56
|
+
@size += 1
|
57
|
+
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(item)
|
62
|
+
if @head.equal?(item)
|
63
|
+
@head = @head.tail
|
64
|
+
else
|
65
|
+
item.head.tail = item.tail
|
66
|
+
end
|
67
|
+
|
68
|
+
if @tail.equal?(item)
|
69
|
+
@tail = @tail.head
|
70
|
+
else
|
71
|
+
item.tail.head = item.head
|
72
|
+
end
|
73
|
+
|
74
|
+
item.head = nil
|
75
|
+
# Don't do this, because it will break enumeration.
|
76
|
+
# item.tail = nil
|
77
|
+
|
78
|
+
@size -= 1
|
79
|
+
|
80
|
+
return self
|
81
|
+
end
|
82
|
+
|
83
|
+
def each
|
84
|
+
return to_enum unless block_given?
|
85
|
+
|
86
|
+
item = @head
|
87
|
+
while item
|
88
|
+
yield item
|
89
|
+
item = item.tail
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def include?(needle)
|
94
|
+
self.each do |item|
|
95
|
+
return true if needle.equal?(item)
|
96
|
+
end
|
97
|
+
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
def first
|
102
|
+
@head
|
103
|
+
end
|
104
|
+
|
105
|
+
def last
|
106
|
+
@tail
|
107
|
+
end
|
108
|
+
|
109
|
+
def empty?
|
110
|
+
@head.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def nil?
|
114
|
+
@head.nil?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private_constant :List
|
119
|
+
|
120
|
+
class Children < List
|
121
|
+
def initialize
|
122
|
+
super
|
123
|
+
|
124
|
+
@transient_count = 0
|
125
|
+
end
|
126
|
+
|
127
|
+
# Does this node have (direct) transient children?
|
128
|
+
def transients?
|
129
|
+
@transient_count > 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def insert(item)
|
133
|
+
if item.transient?
|
134
|
+
@transient_count += 1
|
135
|
+
end
|
136
|
+
|
137
|
+
super
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete(item)
|
141
|
+
if item.transient?
|
142
|
+
@transient_count -= 1
|
143
|
+
end
|
144
|
+
|
145
|
+
super
|
146
|
+
end
|
147
|
+
|
148
|
+
def finished?
|
149
|
+
@size == @transient_count
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
26
153
|
# Represents a node in a tree, used for nested {Task} instances.
|
27
154
|
class Node
|
28
155
|
# Create a new node in the tree.
|
29
156
|
# @param parent [Node, nil] This node will attach to the given parent.
|
30
157
|
def initialize(parent = nil, annotation: nil, transient: false)
|
31
|
-
@children = nil
|
32
158
|
@parent = nil
|
33
|
-
|
34
|
-
# The number of transient children:
|
35
|
-
@transients = 0
|
159
|
+
@children = nil
|
36
160
|
|
37
161
|
@annotation = annotation
|
38
162
|
@object_name = nil
|
39
163
|
|
40
164
|
@transient = transient
|
41
165
|
|
166
|
+
@head = nil
|
167
|
+
@tail = nil
|
168
|
+
|
42
169
|
if parent
|
43
|
-
self
|
170
|
+
parent.add_child(self)
|
44
171
|
end
|
45
172
|
end
|
46
173
|
|
174
|
+
# You should not directly rely on these pointers but instead use `#children`.
|
175
|
+
# List pointers:
|
176
|
+
attr_accessor :head
|
177
|
+
attr_accessor :tail
|
178
|
+
|
47
179
|
# @attr parent [Node, nil]
|
48
180
|
attr :parent
|
49
181
|
|
50
|
-
# @attr children [
|
182
|
+
# @attr children [List<Node>] Optional list of children.
|
51
183
|
attr :children
|
52
184
|
|
53
185
|
# A useful identifier for the current node.
|
54
186
|
attr :annotation
|
55
187
|
|
188
|
+
# Whether there are children?
|
189
|
+
def children?
|
190
|
+
@children != nil && !@children.empty?
|
191
|
+
end
|
192
|
+
|
56
193
|
# Is this node transient?
|
57
194
|
def transient?
|
58
195
|
@transient
|
59
196
|
end
|
60
197
|
|
61
|
-
# Does this node have (direct) transient children?
|
62
|
-
def transients?
|
63
|
-
@transients > 0
|
64
|
-
end
|
65
|
-
|
66
198
|
def annotate(annotation)
|
67
199
|
if block_given?
|
68
200
|
previous_annotation = @annotation
|
@@ -95,13 +227,12 @@ module Async
|
|
95
227
|
return if @parent.equal?(parent)
|
96
228
|
|
97
229
|
if @parent
|
98
|
-
@parent.
|
230
|
+
@parent.delete_child(self)
|
99
231
|
@parent = nil
|
100
232
|
end
|
101
233
|
|
102
234
|
if parent
|
103
|
-
|
104
|
-
@parent.add_child(self)
|
235
|
+
parent.add_child(self)
|
105
236
|
end
|
106
237
|
|
107
238
|
return self
|
@@ -112,52 +243,42 @@ module Async
|
|
112
243
|
end
|
113
244
|
|
114
245
|
protected def add_child child
|
115
|
-
@children ||=
|
116
|
-
@children
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
246
|
+
@children ||= Children.new
|
247
|
+
@children.insert(child)
|
248
|
+
child.set_parent(self)
|
249
|
+
end
|
250
|
+
|
251
|
+
protected def delete_child(child)
|
252
|
+
@children.delete(child)
|
253
|
+
child.set_parent(nil)
|
121
254
|
end
|
122
255
|
|
123
256
|
# Whether the node can be consumed safely. By default, checks if the
|
124
257
|
# children set is empty.
|
125
258
|
# @return [Boolean]
|
126
259
|
def finished?
|
127
|
-
@children.nil? || @children.
|
260
|
+
@children.nil? || @children.finished?
|
128
261
|
end
|
129
262
|
|
130
263
|
# If the node has a parent, and is {finished?}, then remove this node from
|
131
264
|
# the parent.
|
132
265
|
def consume
|
133
|
-
if @parent
|
134
|
-
|
266
|
+
if parent = @parent and finished?
|
267
|
+
parent.delete_child(self)
|
135
268
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# @param child [Node]
|
147
|
-
def reap(child)
|
148
|
-
@children.delete(child)
|
149
|
-
|
150
|
-
if child.transient?
|
151
|
-
@transients -= 1
|
152
|
-
end
|
153
|
-
|
154
|
-
child.children&.each do |grand_child|
|
155
|
-
if grand_child.finished?
|
156
|
-
grand_child.set_parent(nil)
|
157
|
-
else
|
158
|
-
grand_child.set_parent(self)
|
159
|
-
add_child(grand_child)
|
269
|
+
if @children
|
270
|
+
@children.each do |child|
|
271
|
+
if child.finished?
|
272
|
+
delete_child(child)
|
273
|
+
else
|
274
|
+
parent.add_child(child)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
@children = nil
|
160
279
|
end
|
280
|
+
|
281
|
+
parent.consume
|
161
282
|
end
|
162
283
|
end
|
163
284
|
|
data/lib/async/reactor.rb
CHANGED
@@ -91,11 +91,11 @@ module Async
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def to_s
|
94
|
-
"\#<#{self.description} #{@children&.size || 0} children #{stopped? ? 'stopped' : 'running'}>"
|
94
|
+
"\#<#{self.description} #{@children&.size || 0} children (#{stopped? ? 'stopped' : 'running'})>"
|
95
95
|
end
|
96
96
|
|
97
97
|
def stopped?
|
98
|
-
@children.nil?
|
98
|
+
@children.nil?
|
99
99
|
end
|
100
100
|
|
101
101
|
# TODO Remove these in next major release. They are too confusing to use correctly.
|
@@ -222,9 +222,7 @@ module Async
|
|
222
222
|
return true
|
223
223
|
end
|
224
224
|
|
225
|
-
# Run the reactor until
|
226
|
-
# invoked. Proxies arguments to {#async} immediately before entering the
|
227
|
-
# loop, if a block is provided.
|
225
|
+
# Run the reactor until all tasks are finished. Proxies arguments to {#async} immediately before entering the loop, if a block is provided.
|
228
226
|
def run(*arguments, &block)
|
229
227
|
raise RuntimeError, 'Reactor has been closed' if @selector.nil?
|
230
228
|
|
@@ -239,12 +237,19 @@ module Async
|
|
239
237
|
logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
|
240
238
|
end
|
241
239
|
|
240
|
+
def stop(later = true)
|
241
|
+
@children&.each do |child|
|
242
|
+
# We don't want this process to propagate `Async::Stop` exceptions, so we schedule tasks to stop later.
|
243
|
+
child.stop(later)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
242
247
|
# Stop each of the children tasks and close the selector.
|
243
248
|
#
|
244
249
|
# @return [void]
|
245
250
|
def close
|
246
251
|
# This is a critical step. Because tasks could be stored as instance variables, and since the reactor is (probably) going out of scope, we need to ensure they are stopped. Otherwise, the tasks will belong to a reactor that will never run again and are not stopped.
|
247
|
-
self.stop
|
252
|
+
self.stop(false)
|
248
253
|
|
249
254
|
@selector.close
|
250
255
|
@selector = nil
|
@@ -255,7 +260,7 @@ module Async
|
|
255
260
|
def closed?
|
256
261
|
@selector.nil?
|
257
262
|
end
|
258
|
-
|
263
|
+
|
259
264
|
# Put the calling fiber to sleep for a given ammount of time.
|
260
265
|
# @param duration [Numeric] The time in seconds, to sleep for.
|
261
266
|
def sleep(duration)
|
data/lib/async/version.rb
CHANGED
data/spec/async/node_spec.rb
CHANGED
@@ -35,14 +35,14 @@ RSpec.describe Async::Node do
|
|
35
35
|
child.parent = nil
|
36
36
|
|
37
37
|
expect(child.parent).to be_nil
|
38
|
-
expect(subject.children).to
|
38
|
+
expect(subject.children).to be_nil
|
39
39
|
end
|
40
40
|
|
41
41
|
it "can consume bottom to top" do
|
42
42
|
child.consume
|
43
43
|
|
44
44
|
expect(child.parent).to be_nil
|
45
|
-
expect(subject.children).to
|
45
|
+
expect(subject.children).to be_nil
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -64,16 +64,34 @@ RSpec.describe Async::Node do
|
|
64
64
|
end
|
65
65
|
|
66
66
|
describe '#consume' do
|
67
|
-
let(:middle) {Async::Node.new(subject)}
|
68
|
-
let(:bottom) {Async::Node.new(middle)}
|
69
|
-
|
70
67
|
it "can't consume middle node" do
|
68
|
+
middle = Async::Node.new(subject)
|
69
|
+
bottom = Async::Node.new(middle)
|
70
|
+
|
71
71
|
expect(bottom.parent).to be middle
|
72
72
|
|
73
73
|
middle.consume
|
74
74
|
|
75
75
|
expect(bottom.parent).to be middle
|
76
76
|
end
|
77
|
+
|
78
|
+
it "can consume node while enumerating children" do
|
79
|
+
3.times do
|
80
|
+
Async::Node.new(subject)
|
81
|
+
end
|
82
|
+
|
83
|
+
children = subject.children.each.to_a
|
84
|
+
expect(subject.children.size).to be 3
|
85
|
+
|
86
|
+
enumerated = []
|
87
|
+
|
88
|
+
subject.children.each do |child|
|
89
|
+
child.consume
|
90
|
+
enumerated << child
|
91
|
+
end
|
92
|
+
|
93
|
+
expect(enumerated).to be == children
|
94
|
+
end
|
77
95
|
end
|
78
96
|
|
79
97
|
describe '#annotate' do
|
@@ -129,9 +147,9 @@ RSpec.describe Async::Node do
|
|
129
147
|
|
130
148
|
# subject -> middle -> child1 (transient)
|
131
149
|
# -> child2
|
132
|
-
middle = Async::Node.new(subject)
|
133
|
-
child1 = Async::Node.new(middle, transient: true)
|
134
|
-
child2 = Async::Node.new(middle)
|
150
|
+
middle = Async::Node.new(subject, annotation: "middle")
|
151
|
+
child1 = Async::Node.new(middle, transient: true, annotation: "child1")
|
152
|
+
child2 = Async::Node.new(middle, annotation: "child2")
|
135
153
|
|
136
154
|
allow(child1).to receive(:finished?).and_return(false)
|
137
155
|
|
@@ -169,7 +187,7 @@ RSpec.describe Async::Node do
|
|
169
187
|
expect(child.parent).to be_nil
|
170
188
|
expect(middle.parent).to be subject
|
171
189
|
expect(subject.children).to include(middle)
|
172
|
-
expect(middle.children).to
|
190
|
+
expect(middle.children).to be_nil
|
173
191
|
end
|
174
192
|
end
|
175
193
|
end
|
data/spec/async/queue_spec.rb
CHANGED
@@ -109,6 +109,7 @@ RSpec.describe Async::LimitedQueue do
|
|
109
109
|
it 'should resume waiting tasks in order' do
|
110
110
|
total_resumed = 0
|
111
111
|
total_dequeued = 0
|
112
|
+
|
112
113
|
Async do |producer|
|
113
114
|
10.times do
|
114
115
|
producer.async do
|
@@ -117,13 +118,12 @@ RSpec.describe Async::LimitedQueue do
|
|
117
118
|
end
|
118
119
|
end
|
119
120
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
121
|
+
|
122
|
+
10.times do
|
123
|
+
item = subject.dequeue
|
124
|
+
total_dequeued += 1
|
125
|
+
|
126
|
+
expect(total_resumed).to be == total_dequeued
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
data/spec/async/reactor_spec.rb
CHANGED
@@ -80,17 +80,37 @@ RSpec.describe Async::Reactor do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
describe '#print_hierarchy' do
|
84
|
+
it "can print hierarchy" do
|
85
|
+
subject.async do |parent|
|
86
|
+
parent.async do |child|
|
87
|
+
child.sleep 1
|
88
|
+
end
|
89
|
+
|
90
|
+
parent.sleep 1
|
91
|
+
end
|
92
|
+
|
93
|
+
output = StringIO.new
|
94
|
+
subject.print_hierarchy(output)
|
95
|
+
lines = output.string.lines
|
96
|
+
|
97
|
+
expect(lines[0]).to be =~ /#<Async::Reactor.*(running)/
|
98
|
+
expect(lines[1]).to be =~ /\t#<Async::Task.*(running)/
|
99
|
+
expect(lines[2]).to be =~ /\t\t#<Async::Task.*(running)/
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
83
103
|
describe '#stop' do
|
84
104
|
it "can stop the reactor" do
|
85
105
|
state = nil
|
86
106
|
|
87
|
-
subject.async do |task|
|
107
|
+
subject.async(annotation: "sleep(10)") do |task|
|
88
108
|
state = :started
|
89
109
|
task.sleep(10)
|
90
110
|
state = :stopped
|
91
111
|
end
|
92
112
|
|
93
|
-
subject.async do |task|
|
113
|
+
subject.async(annotation: "reactor.stop") do |task|
|
94
114
|
task.sleep(0.1)
|
95
115
|
task.reactor.stop
|
96
116
|
end
|
data/spec/async/task_spec.rb
CHANGED
@@ -447,6 +447,16 @@ RSpec.describe Async::Task do
|
|
447
447
|
end
|
448
448
|
end
|
449
449
|
|
450
|
+
describe '#children' do
|
451
|
+
it "enumerates children in same order they are created" do
|
452
|
+
tasks = 10.times.map do |i|
|
453
|
+
reactor.async(annotation: "Task #{i}") {|task| task.sleep(1)}
|
454
|
+
end
|
455
|
+
|
456
|
+
expect(reactor.children.each.to_a).to be == tasks
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
450
460
|
describe '#to_s' do
|
451
461
|
it "should show running" do
|
452
462
|
apples_task = reactor.async do |task|
|
data/spec/spec_helper.rb
CHANGED
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.25.
|
4
|
+
version: 1.25.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nio4r
|