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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e483e124943c2ca198118249cfd157307afd86a6e9ead4b376be27ffc95b645
4
- data.tar.gz: c460b159f280389b26237c41fd20bac56d9e5b2c525f884402f6170b85fd5053
3
+ metadata.gz: 70efaa49b29fd36596f22f7796c475610955cc63836725b00775f6c1025aa976
4
+ data.tar.gz: d5e7987bcb581c20b3ac17fba0ef803a61f154ec107b55ad01220c16ae2a32e6
5
5
  SHA512:
6
- metadata.gz: 482ff867811aa68736b37a50d83e5850f4a1f9a310274b8993898c5577136e7502f5c369a3c9bf4685380b88a71de2ff4ed04fbd92115e8918472522e92786ea
7
- data.tar.gz: 37584a2af7140b2a4efa0fb60879e97ddae99879f97b137add7fb62662139300e280a9126cf24f559172ecb7ba1f2a6c21eb77adc317df9ae54f9ddb8378f928
6
+ metadata.gz: d47987f2b355ab4dff7e06443787190dc8567abe30391b3a0a2dde55efb4592c14af94b42f52cf2b7f6e35d9868bb1f2cfbdd23cb9abddb4c1ba34a97cf0dab9
7
+ data.tar.gz: 014fa5e68d7fb509f6120cbce6e07f75d6c17c86b8d5dcc93278555c4e11ea27a2a8883ba8193359aeed1ab3d5303d92e30b053cf840aa31c0b4f9b002d7c5d4
@@ -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.parent = parent
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 [Set<Node>] Optional list of 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.reap(self)
230
+ @parent.delete_child(self)
99
231
  @parent = nil
100
232
  end
101
233
 
102
234
  if parent
103
- @parent = parent
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 ||= Set.new
116
- @children << child
117
-
118
- if child.transient?
119
- @transients += 1
120
- end
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.empty? || (@children.size == @transients)
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 && finished?
134
- @parent.reap(self)
266
+ if parent = @parent and finished?
267
+ parent.delete_child(self)
135
268
 
136
- # After reaping self, children are all moved elsewhere.
137
- @children = nil
138
- @transients = 0
139
-
140
- @parent.consume
141
- @parent = nil
142
- end
143
- end
144
-
145
- # Remove a given child node.
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
 
@@ -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? || @children.empty?
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 either all tasks complete or {#pause} or {#stop} is
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)
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Async
24
- VERSION = "1.25.0"
24
+ VERSION = "1.25.1"
25
25
  end
@@ -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 be_empty
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 be_empty
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 be_empty
190
+ expect(middle.children).to be_nil
173
191
  end
174
192
  end
175
193
  end
@@ -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
- Async do |consumer|
121
- 10.times do
122
- subject.dequeue
123
- total_dequeued += 1
124
-
125
- expect(total_resumed).to be == total_dequeued
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
@@ -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
@@ -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|
@@ -3,7 +3,7 @@
3
3
  require "covered/rspec"
4
4
 
5
5
  if RUBY_PLATFORM =~ /darwin/
6
- Q = 10
6
+ Q = 20
7
7
  else
8
8
  Q = 1
9
9
  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.25.0
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-23 00:00:00.000000000 Z
11
+ date: 2020-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r