async 1.28.6 → 1.29.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: 6f37f30a9b3c4eecb9dd393813bd30545b4c9e07529be6fda43f50c63c35bc53
4
- data.tar.gz: 12e98fb9b1758199fd7e06a1ae4ac1e6b696844ceb42f75827905bd216a2b9fa
3
+ metadata.gz: 94443a93442232e8189364cd931084a0cf188a9adea3fe91287f192fed44fc2e
4
+ data.tar.gz: cec386ffc8948123aeb771866d639b9e49a66cdb8e32fa42e15f07bbd06bd78e
5
5
  SHA512:
6
- metadata.gz: bf2e4696aae38b3c9e61ea1cba7c9eb115bab9646eae5e68459cc6f82a4b9e34010a6bd111f5983c2e902dd1f8db2ffafae56d3b520698d923cabf539ed35f39
7
- data.tar.gz: c71f02fa817c1d21dbefe64ade8aea5cbdfc410550c46aee478fa36a040e099a6ac137d59c32f591acc1d3aed67c23bf6b0e0e89d7f0a84fbadc9c10710fdd54
6
+ metadata.gz: 3cf4de1694de02a5c291b124d1ee62020b8ab8997a1e951977e6450a526d00a236918af520e1e0c4e63036c50731a07932d734b8a1f4b825f17661cb30f23e0f
7
+ data.tar.gz: 0c157b4af9bdd4a3ade74ccc12a4f2ea79162d45395d28e34c30e2d7cb7535b9137d37ed0688852b0b52834cf38c7a4207b11797deaa201d08d227c4a6e714ce
@@ -20,25 +20,23 @@
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 'delegate'
24
+
23
25
  module Async
24
26
  module Debug
25
- class Monitor
27
+ class Monitor < Delegator
26
28
  def initialize(monitor, selector)
27
29
  @monitor = monitor
28
30
  @selector = selector
29
31
  end
30
32
 
31
- def close
32
- @selector.deregister(@monitor.io)
33
- @monitor.close
33
+ def __getobj__
34
+ @monitor
34
35
  end
35
36
 
36
- def method_missing(*arguments, &block)
37
- @monitor.send(*arguments)
38
- end
39
-
40
- def respond_to?(*arguments)
41
- @monitor.respond_to?(*arguments)
37
+ def close
38
+ @selector.deregister(self)
39
+ @monitor.close
42
40
  end
43
41
 
44
42
  def inspect
@@ -24,6 +24,7 @@ require_relative 'monitor'
24
24
  require_relative '../logger'
25
25
 
26
26
  require 'nio'
27
+ require 'set'
27
28
 
28
29
  module Async
29
30
  module Debug
@@ -36,7 +37,7 @@ module Async
36
37
  class Selector
37
38
  def initialize(selector = NIO::Selector.new)
38
39
  @selector = selector
39
- @monitors = {}
40
+ @monitors = Set.new
40
41
  end
41
42
 
42
43
  def register(object, interests)
@@ -46,26 +47,18 @@ module Async
46
47
  raise RuntimeError, "Could not convert #{io} into IO!"
47
48
  end
48
49
 
49
- if monitor = @monitors[io.fileno]
50
- raise RuntimeError, "Trying to register monitor for #{object.inspect} but it was already registered: #{monitor.inspect}!"
51
- end
52
-
53
50
  monitor = Monitor.new(@selector.register(object, interests), self)
54
51
 
55
- @monitors[io.fileno] = monitor
52
+ @monitors.add(monitor)
56
53
 
57
54
  return monitor
58
55
  end
59
56
 
60
- def deregister(object)
61
- Async.logger.debug(self) {"Deregistering #{object.inspect}."}
62
-
63
- unless io = ::IO.try_convert(object)
64
- raise RuntimeError, "Could not convert #{io} into IO!"
65
- end
57
+ def deregister(monitor)
58
+ Async.logger.debug(self) {"Deregistering #{monitor.inspect}."}
66
59
 
67
- unless @monitors.delete(io.fileno)
68
- raise RuntimeError, "Trying to remove monitor for #{io.inspect} but it was not registered!"
60
+ unless @monitors.delete?(monitor)
61
+ raise RuntimeError, "Trying to remove monitor for #{monitor.inspect} but it was not registered!"
69
62
  end
70
63
  end
71
64
 
@@ -75,7 +68,7 @@ module Async
75
68
 
76
69
  def close
77
70
  if @monitors.any?
78
- raise LeakError, @monitors.values
71
+ raise LeakError, @monitors
79
72
  end
80
73
  ensure
81
74
  @selector.close
data/lib/async/node.rb CHANGED
@@ -24,6 +24,7 @@ module Async
24
24
  # A double linked list.
25
25
  class List
26
26
  def initialize
27
+ # The list behaves like a list node, so @tail points to the next item (the first one) and head points to the previous item (the last one). This may be slightly confusing but it makes the interface more natural.
27
28
  @head = nil
28
29
  @tail = nil
29
30
  @size = 0
@@ -36,21 +37,21 @@ module Async
36
37
 
37
38
  # Inserts an item at the end of the list.
38
39
  def insert(item)
39
- unless @head
40
- @head = item
40
+ unless @tail
41
41
  @tail = item
42
+ @head = item
42
43
 
43
44
  # Consistency:
44
45
  item.head = nil
45
46
  item.tail = nil
46
47
  else
47
- @tail.tail = item
48
- item.head = @tail
48
+ @head.tail = item
49
+ item.head = @head
49
50
 
50
51
  # Consistency:
51
52
  item.tail = nil
52
53
 
53
- @tail = item
54
+ @head = item
54
55
  end
55
56
 
56
57
  @size += 1
@@ -59,14 +60,14 @@ module Async
59
60
  end
60
61
 
61
62
  def delete(item)
62
- if @head.equal?(item)
63
- @head = @head.tail
63
+ if @tail.equal?(item)
64
+ @tail = @tail.tail
64
65
  else
65
66
  item.head.tail = item.tail
66
67
  end
67
68
 
68
- if @tail.equal?(item)
69
- @tail = @tail.head
69
+ if @head.equal?(item)
70
+ @head = @head.head
70
71
  else
71
72
  item.tail.head = item.head
72
73
  end
@@ -79,15 +80,17 @@ module Async
79
80
  return self
80
81
  end
81
82
 
82
- def each
83
+ def each(&block)
83
84
  return to_enum unless block_given?
84
85
 
85
- item = @head
86
- while item
87
- # We store the tail pointer so we can remove the current item from the linked list:
88
- tail = item.tail
89
- yield item
90
- item = tail
86
+ current = self
87
+ while node = current.tail
88
+ yield node
89
+
90
+ # If the node has deleted itself or any subsequent node, it will no longer be the next node, so don't use it for continued traversal:
91
+ if current.tail.equal?(node)
92
+ current = node
93
+ end
91
94
  end
92
95
  end
93
96
 
@@ -100,19 +103,19 @@ module Async
100
103
  end
101
104
 
102
105
  def first
103
- @head
106
+ @tail
104
107
  end
105
108
 
106
109
  def last
107
- @tail
110
+ @head
108
111
  end
109
112
 
110
113
  def empty?
111
- @head.nil?
114
+ @tail.nil?
112
115
  end
113
116
 
114
117
  def nil?
115
- @head.nil?
118
+ @tail.nil?
116
119
  end
117
120
  end
118
121
 
@@ -276,6 +279,8 @@ module Async
276
279
  if child.finished?
277
280
  delete_child(child)
278
281
  else
282
+ # In theory we don't need to do this... because we are throwing away the list. However, if you don't correctly update the list when moving the child to the parent, it foobars the enumeration, and subsequent nodes will be skipped, or in the worst case you might start enumerating the parents nodes.
283
+ delete_child(child)
279
284
  parent.add_child(child)
280
285
  end
281
286
  end
@@ -297,8 +302,31 @@ module Async
297
302
  end
298
303
  end
299
304
 
300
- def stop
301
- @children&.each(&:stop)
305
+ # Immediately terminate all children tasks, including transient tasks.
306
+ # Internally invokes `stop(false)` on all children.
307
+ def terminate
308
+ # Attempt to stop the current task immediately, and all children:
309
+ stop(false)
310
+
311
+ # If that doesn't work, take more serious action:
312
+ @children&.each do |child|
313
+ child.terminate
314
+ end
315
+ end
316
+
317
+ # Attempt to stop the current node immediately, including all non-transient children.
318
+ # Invokes {#stop_children} to stop all children.
319
+ # @parameter later [Boolean] Whether to defer stopping until some point in the future.
320
+ def stop(later = false)
321
+ # The implementation of this method may defer calling `stop_children`.
322
+ stop_children(later)
323
+ end
324
+
325
+ # Attempt to stop all non-transient children.
326
+ private def stop_children(later = false)
327
+ @children&.each do |child|
328
+ child.stop(later) unless child.transient?
329
+ end
302
330
  end
303
331
 
304
332
  def print_hierarchy(out = $stdout, backtrace: true)
data/lib/async/reactor.rb CHANGED
@@ -47,9 +47,7 @@ module Async
47
47
  # running.
48
48
  def self.run(*arguments, **options, &block)
49
49
  if current = Task.current?
50
- reactor = current.reactor
51
-
52
- return reactor.async(*arguments, **options, &block)
50
+ return current.async(*arguments, **options, &block)
53
51
  else
54
52
  reactor = self.new
55
53
 
@@ -96,6 +94,7 @@ module Async
96
94
  end
97
95
 
98
96
  attr :scheduler
97
+ attr :logger
99
98
 
100
99
  # @reentrant Not thread safe.
101
100
  def block(blocker, timeout)
@@ -111,7 +110,7 @@ module Async
111
110
 
112
111
  begin
113
112
  @blocked += 1
114
- Fiber.yield
113
+ Task.yield
115
114
  ensure
116
115
  @blocked -= 1
117
116
  end
@@ -135,10 +134,6 @@ module Async
135
134
  end
136
135
  end
137
136
 
138
- def logger
139
- @logger || Console.logger
140
- end
141
-
142
137
  def to_s
143
138
  "\#<#{self.description} #{@children&.size || 0} children (#{stopped? ? 'stopped' : 'running'})>"
144
139
  end
@@ -167,7 +162,7 @@ module Async
167
162
  # - Avoid scheduler overhead if no blocking operation is performed.
168
163
  task.run(*arguments)
169
164
 
170
- # logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
165
+ # Console.logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
171
166
  return task
172
167
  end
173
168
 
@@ -198,7 +193,7 @@ module Async
198
193
  def yield(fiber = Fiber.current)
199
194
  @ready << fiber
200
195
 
201
- Fiber.yield
196
+ Task.yield
202
197
  end
203
198
 
204
199
  def finished?
@@ -210,7 +205,7 @@ module Async
210
205
  # @param timeout [Float | nil] the maximum timeout, or if nil, indefinite.
211
206
  # @return [Boolean] whether there is more work to do.
212
207
  def run_once(timeout = nil)
213
- # logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
208
+ # Console.logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
214
209
 
215
210
  if @ready.any?
216
211
  # running used to correctly answer on `finished?`, and to reuse Array object.
@@ -258,7 +253,7 @@ module Async
258
253
  interval = timeout
259
254
  end
260
255
 
261
- # logger.info(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
256
+ # Console.logger.info(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
262
257
  if monitors = @selector.select(interval)
263
258
  monitors.each do |monitor|
264
259
  monitor.value.resume
@@ -295,35 +290,26 @@ module Async
295
290
  return initial_task
296
291
  ensure
297
292
  @scheduler&.clear!
298
- logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
299
- end
300
-
301
- def stop(later = true)
302
- @children&.each do |child|
303
- # We don't want this process to propagate `Async::Stop` exceptions, so we schedule tasks to stop later.
304
- child.stop(later)
305
- end
293
+ Console.logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
306
294
  end
307
295
 
308
296
  # Stop each of the children tasks and close the selector.
309
- #
310
- # @return [void]
311
297
  def close
312
- # 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.
313
- self.stop(false)
298
+ # 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:
299
+ self.terminate
314
300
 
315
301
  @selector.close
316
302
  @selector = nil
317
303
  end
318
304
 
319
305
  # Check if the selector has been closed.
320
- # @return [Boolean]
306
+ # @returns [Boolean]
321
307
  def closed?
322
308
  @selector.nil?
323
309
  end
324
310
 
325
311
  # Put the calling fiber to sleep for a given ammount of time.
326
- # @param duration [Numeric] The time in seconds, to sleep for.
312
+ # @parameter duration [Numeric] The time in seconds, to sleep for.
327
313
  def sleep(duration)
328
314
  fiber = Fiber.current
329
315
 
@@ -347,7 +333,7 @@ module Async
347
333
  timer = @timers.after(timeout) do
348
334
  if fiber.alive?
349
335
  error = exception.new("execution expired")
350
- fiber.resume error
336
+ fiber.resume(error)
351
337
  end
352
338
  end
353
339
 
@@ -68,6 +68,8 @@ module Async
68
68
  end
69
69
 
70
70
  return false
71
+ rescue TimeoutError
72
+ return nil
71
73
  ensure
72
74
  wrapper.reactor = nil
73
75
  end
data/lib/async/task.rb CHANGED
@@ -81,11 +81,13 @@ module Async
81
81
  @result = nil
82
82
  @finished = finished
83
83
 
84
- @logger = logger
84
+ @logger = logger || @parent.logger
85
85
 
86
86
  @fiber = make_fiber(&block)
87
87
  end
88
88
 
89
+ attr :logger
90
+
89
91
  if Fiber.current.respond_to?(:backtrace)
90
92
  def backtrace(*arguments)
91
93
  @fiber&.backtrace(*arguments)
@@ -96,10 +98,6 @@ module Async
96
98
  "\#<#{self.description} (#{@status})>"
97
99
  end
98
100
 
99
- def logger
100
- @logger || Console.logger
101
- end
102
-
103
101
  # @attr ios [Reactor] The reactor the task was created within.
104
102
  attr :reactor
105
103
 
@@ -158,7 +156,6 @@ module Async
158
156
  # Soon to become attr :result
159
157
 
160
158
  # Stop the task and all of its children.
161
- # @return [void]
162
159
  def stop(later = false)
163
160
  if self.stopped?
164
161
  # If we already stopped this task... don't try to stop it again:
@@ -242,9 +239,9 @@ module Async
242
239
  raise
243
240
  elsif @finished.nil?
244
241
  # If no one has called wait, we log this as an error:
245
- logger.error(self) {$!}
242
+ Console.logger.error(self) {$!}
246
243
  else
247
- logger.debug(self) {$!}
244
+ Console.logger.debug(self) {$!}
248
245
  end
249
246
  end
250
247
 
@@ -252,9 +249,7 @@ module Async
252
249
  # logger.debug(self) {"Task was stopped with #{@children&.size.inspect} children!"}
253
250
  @status = :stopped
254
251
 
255
- @children&.each do |child|
256
- child.stop(true)
257
- end
252
+ stop_children(true)
258
253
  end
259
254
 
260
255
  def make_fiber(&block)
@@ -264,7 +259,7 @@ module Async
264
259
  begin
265
260
  @result = yield(self, *arguments)
266
261
  @status = :complete
267
- # logger.debug(self) {"Task was completed with #{@children.size} children!"}
262
+ # Console.logger.debug(self) {"Task was completed with #{@children.size} children!"}
268
263
  rescue Stop
269
264
  stop!
270
265
  rescue StandardError => error
@@ -272,7 +267,7 @@ module Async
272
267
  rescue Exception => exception
273
268
  fail!(exception, true)
274
269
  ensure
275
- # logger.debug(self) {"Task ensure $!=#{$!} with #{@children.size} children!"}
270
+ # Console.logger.debug(self) {"Task ensure $!=#{$!} with #{@children.size} children!"}
276
271
  finish!
277
272
  end
278
273
  end
data/lib/async/version.rb CHANGED
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Async
24
- VERSION = "1.28.6"
24
+ VERSION = "1.29.1"
25
25
  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.28.6
4
+ version: 1.29.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: 2021-02-07 00:00:00.000000000 Z
11
+ date: 2021-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: console
@@ -179,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
181
  requirements: []
182
- rubygems_version: 3.2.3
182
+ rubygems_version: 3.2.15
183
183
  signing_key:
184
184
  specification_version: 4
185
185
  summary: A concurrency framework for Ruby.