async 2.12.1 → 2.16.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: 1eef4bf800b6d52c0f273289186f9886975237ecdf9885f599617691283ca2e5
4
- data.tar.gz: 14a7e20dc72c178b89a93233a73fd49f182f7aec7926804b9b8bf37e8ceb9f36
3
+ metadata.gz: cd3699fb686c1acc86c39923b81f946f30be577b19eedde83ccb18c156490bfa
4
+ data.tar.gz: 27f7af6e0106d94fc8d0a6da188d6c923bc9afce09a32fddad1258367f9d683c
5
5
  SHA512:
6
- metadata.gz: 30176358a5c9efc3df3e525e0ba941f5771bb7619bef28e1b6229a547ad3a24cb5475483a257704c3eea2d9214dcca2e17932994e0859a1c5d4dfc6401b24d48
7
- data.tar.gz: 8b7353cf2ef0884c78a919775b84280d3234c7868c6c3b95aa00ae560a5825a07bd9533eda1afcc9776d1c94b7cc116e549e24144e4bab7c9bb3f086782a7456
6
+ metadata.gz: 3e3d03ffcf1b3f7f3d3a4d8f3285092e4a6a6367f91259fbc17aba628d2aa6c8dbbbdcae0093a129a4a7f942f9bcf805d29cfe4e0cd715627ec62098a0e91303
7
+ data.tar.gz: 65c681adf6715d125e270b323a39db62cf91efd22628884f48131b68f639ffdd5acc69402181b33f63104a2fb648135f327b2abce8a39d366024be0ca7c30665
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2022, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
 
7
7
  require 'fiber'
@@ -11,6 +11,7 @@ module Async
11
11
  # A synchronization primitive, which allows fibers to wait until a particular condition is (edge) triggered.
12
12
  # @public Since `stable-v1`.
13
13
  class Condition
14
+ # Create a new condition.
14
15
  def initialize
15
16
  @waiting = List.new
16
17
  end
@@ -39,12 +40,16 @@ module Async
39
40
  end
40
41
  end
41
42
 
42
- # Is any fiber waiting on this notification?
43
- # @returns [Boolean]
43
+ # @deprecated Replaced by {#waiting?}
44
44
  def empty?
45
45
  @waiting.empty?
46
46
  end
47
47
 
48
+ # @returns [Boolean] Is any fiber waiting on this notification?
49
+ def waiting?
50
+ @waiting.size > 0
51
+ end
52
+
48
53
  # Signal to a given task that it should resume operations.
49
54
  # @parameter value [Object | Nil] The value to return to the waiting fibers.
50
55
  def signal(value = nil)
data/lib/async/idler.rb CHANGED
@@ -4,13 +4,28 @@
4
4
  # Copyright, 2024, by Samuel Williams.
5
5
 
6
6
  module Async
7
+ # A load balancing mechanism that can be used process work when the system is idle.
7
8
  class Idler
9
+ # Create a new idler.
10
+ # @public Since `stable-v2`.
11
+ #
12
+ # @parameter maximum_load [Numeric] The maximum load before we start shedding work.
13
+ # @parameter backoff [Numeric] The initial backoff time, used for delaying work.
14
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
8
15
  def initialize(maximum_load = 0.8, backoff: 0.01, parent: nil)
9
16
  @maximum_load = maximum_load
10
17
  @backoff = backoff
11
18
  @parent = parent
12
19
  end
13
20
 
21
+ # Wait until the system is idle, then execute the given block in a new task.
22
+ #
23
+ # @asynchronous Executes the given block concurrently.
24
+ #
25
+ # @parameter arguments [Array] The arguments to pass to the block.
26
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
27
+ # @parameter options [Hash] The options to pass to the task.
28
+ # @yields {|task| ...} When the system is idle, the block will be executed in a new task.
14
29
  def async(*arguments, parent: (@parent or Task.current), **options, &block)
15
30
  wait
16
31
 
@@ -18,12 +33,16 @@ module Async
18
33
  parent.async(*arguments, **options, &block)
19
34
  end
20
35
 
36
+ # Wait until the system is idle, according to the maximum load specified.
37
+ #
38
+ # If the scheduler is overloaded, this method will sleep for an exponentially increasing amount of time.
21
39
  def wait
22
40
  scheduler = Fiber.scheduler
23
41
  backoff = nil
24
42
 
25
43
  while true
26
- load = scheduler.load
44
+ load = scheduler.load
45
+
27
46
  break if load < @maximum_load
28
47
 
29
48
  if backoff
data/lib/async/list.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2024, by Samuel Williams.
5
5
 
6
6
  module Async
7
7
  # A general doublely linked list. This is used internally by {Async::Barrier} and {Async::Condition} to manage child tasks.
@@ -13,7 +13,7 @@ module Async
13
13
  @size = 0
14
14
  end
15
15
 
16
- # Print a short summary of the list.
16
+ # @returns [String] A short summary of the list.
17
17
  def to_s
18
18
  sprintf("#<%s:0x%x size=%d>", self.class.name, object_id, @size)
19
19
  end
@@ -36,12 +36,13 @@ module Async
36
36
  return items
37
37
  end
38
38
 
39
- # Points at the end of the list.
39
+ # @attribute [Node | Nil] Points at the end of the list.
40
40
  attr_accessor :head
41
41
 
42
- # Points at the start of the list.
42
+ # @attribute [Node | Nil] Points at the start of the list.
43
43
  attr_accessor :tail
44
44
 
45
+ # @attribute [Integer] The number of nodes in the list.
45
46
  attr :size
46
47
 
47
48
  # A callback that is invoked when an item is added to the list.
@@ -64,6 +65,7 @@ module Async
64
65
  return added(node)
65
66
  end
66
67
 
68
+ # Prepend a node to the start of the list.
67
69
  def prepend(node)
68
70
  if node.head
69
71
  raise ArgumentError, "Node is already in a list!"
@@ -224,6 +226,7 @@ module Async
224
226
  return nil
225
227
  end
226
228
 
229
+ # Shift the first node off the list, if it is not empty.
227
230
  def shift
228
231
  if node = first
229
232
  remove!(node)
data/lib/async/node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2023, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2022, by Shannon Skipper.
7
7
 
@@ -12,6 +12,7 @@ require_relative 'list'
12
12
  module Async
13
13
  # A list of children tasks.
14
14
  class Children < List
15
+ # Create an empty list of children tasks.
15
16
  def initialize
16
17
  super
17
18
  @transient_count = 0
@@ -33,6 +34,19 @@ module Async
33
34
  empty?
34
35
  end
35
36
 
37
+ # Adjust the number of transient children, assuming it has changed.
38
+ #
39
+ # Despite being public, this is not intended to be called directly. It is used internally by {Node#transient=}.
40
+ #
41
+ # @parameter transient [Boolean] Whether to increment or decrement the transient count.
42
+ def adjust_transient_count(transient)
43
+ if transient
44
+ @transient_count += 1
45
+ else
46
+ @transient_count -= 1
47
+ end
48
+ end
49
+
36
50
  private
37
51
 
38
52
  def added(node)
@@ -73,7 +87,7 @@ module Async
73
87
  end
74
88
  end
75
89
 
76
- # @returns [Node] the root node in the hierarchy.
90
+ # @returns [Node] The root node in the hierarchy.
77
91
  def root
78
92
  @parent&.root || self
79
93
  end
@@ -87,10 +101,10 @@ module Async
87
101
  # @attribute [Node] The parent node.
88
102
  attr :parent
89
103
 
90
- # @attribute children [Children | Nil] Optional list of children.
104
+ # @attribute [Children | Nil] Optional list of children.
91
105
  attr :children
92
106
 
93
- # A useful identifier for the current node.
107
+ # @attribute [String | Nil] A useful identifier for the current node.
94
108
  attr :annotation
95
109
 
96
110
  # Whether this node has any children.
@@ -109,6 +123,22 @@ module Async
109
123
  @transient
110
124
  end
111
125
 
126
+ # Change the transient state of the node.
127
+ #
128
+ # A transient node is not considered when determining if a node is finished, and propagates up if the parent is consumed.
129
+ #
130
+ # @parameter value [Boolean] Whether the node is transient.
131
+ def transient=(value)
132
+ if @transient != value
133
+ @transient = value
134
+
135
+ @parent&.children&.adjust_transient_count(value)
136
+ end
137
+ end
138
+
139
+ # Annotate the node with a description.
140
+ #
141
+ # @parameter annotation [String] The description to annotate the node with.
112
142
  def annotate(annotation)
113
143
  if block_given?
114
144
  begin
@@ -123,6 +153,9 @@ module Async
123
153
  end
124
154
  end
125
155
 
156
+ # A description of the node, including the annotation and object name.
157
+ #
158
+ # @returns [String] The description of the node.
126
159
  def description
127
160
  @object_name ||= "#{self.class}:#{format '%#018x', object_id}#{@transient ? ' transient' : nil}"
128
161
 
@@ -135,10 +168,14 @@ module Async
135
168
  end
136
169
  end
137
170
 
171
+ # Provides a backtrace for nodes that have an active execution context.
172
+ #
173
+ # @returns [Array(Thread::Backtrace::Locations) | Nil] The backtrace of the node, if available.
138
174
  def backtrace(*arguments)
139
175
  nil
140
176
  end
141
177
 
178
+ # @returns [String] A description of the node.
142
179
  def to_s
143
180
  "\#<#{self.description}>"
144
181
  end
@@ -255,10 +292,15 @@ module Async
255
292
  end
256
293
  end
257
294
 
295
+ # Whether the node has been stopped.
258
296
  def stopped?
259
297
  @children.nil?
260
298
  end
261
299
 
300
+ # Print the hierarchy of the task tree from the given node.
301
+ #
302
+ # @parameter out [IO] The output stream to write to.
303
+ # @parameter backtrace [Boolean] Whether to print the backtrace of each node.
262
304
  def print_hierarchy(out = $stdout, backtrace: true)
263
305
  self.traverse do |node, level|
264
306
  indent = "\t" * level
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'condition'
7
7
 
@@ -29,5 +29,7 @@ module Async
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ private_constant :Signal
32
34
  end
33
35
  end
data/lib/async/queue.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2019, by Ryan Musgrave.
6
6
  # Copyright, 2020-2022, by Bruno Sutic.
7
7
 
@@ -9,68 +9,104 @@ require_relative 'notification'
9
9
 
10
10
  module Async
11
11
  # A queue which allows items to be processed in order.
12
+ #
13
+ # It has a compatible interface with {Notification} and {Condition}, except that it's multi-value.
14
+ #
12
15
  # @public Since `stable-v1`.
13
- class Queue < Notification
14
- def initialize(parent: nil)
15
- super()
16
-
16
+ class Queue
17
+ # Create a new queue.
18
+ #
19
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
20
+ # @parameter available [Notification] The notification to use for signaling when items are available.
21
+ def initialize(parent: nil, available: Notification.new)
17
22
  @items = []
18
23
  @parent = parent
24
+ @available = available
19
25
  end
20
26
 
27
+ # @attribute [Array] The items in the queue.
21
28
  attr :items
22
29
 
30
+ # @returns [Integer] The number of items in the queue.
23
31
  def size
24
32
  @items.size
25
33
  end
26
-
34
+
35
+ # @returns [Boolean] Whether the queue is empty.
27
36
  def empty?
28
37
  @items.empty?
29
38
  end
30
39
 
40
+ # Add an item to the queue.
31
41
  def <<(item)
32
42
  @items << item
33
43
 
34
- self.signal unless self.empty?
44
+ @available.signal unless self.empty?
35
45
  end
36
46
 
47
+ # Add multiple items to the queue.
37
48
  def enqueue(*items)
38
49
  @items.concat(items)
39
50
 
40
- self.signal unless self.empty?
51
+ @available.signal unless self.empty?
41
52
  end
42
53
 
54
+ # Remove and return the next item from the queue.
43
55
  def dequeue
44
56
  while @items.empty?
45
- self.wait
57
+ @available.wait
46
58
  end
47
59
 
48
60
  @items.shift
49
61
  end
50
62
 
51
- def async(parent: (@parent or Task.current), &block)
63
+ # Process each item in the queue.
64
+ #
65
+ # @asynchronous Executes the given block concurrently for each item.
66
+ #
67
+ # @parameter arguments [Array] The arguments to pass to the block.
68
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
69
+ # @parameter options [Hash] The options to pass to the task.
70
+ # @yields {|task| ...} When the system is idle, the block will be executed in a new task.
71
+ def async(parent: (@parent or Task.current), **options, &block)
52
72
  while item = self.dequeue
53
- parent.async(item, &block)
73
+ parent.async(item, **options, &block)
54
74
  end
55
75
  end
56
76
 
77
+ # Enumerate each item in the queue.
57
78
  def each
58
79
  while item = self.dequeue
59
80
  yield item
60
81
  end
61
82
  end
83
+
84
+ # Signal the queue with a value, the same as {#enqueue}.
85
+ def signal(value)
86
+ self.enqueue(value)
87
+ end
88
+
89
+ # Wait for an item to be available, the same as {#dequeue}.
90
+ def wait
91
+ self.dequeue
92
+ end
62
93
  end
63
94
 
95
+ # A queue which limits the number of items that can be enqueued.
64
96
  # @public Since `stable-v1`.
65
97
  class LimitedQueue < Queue
66
- def initialize(limit = 1, **options)
98
+ # Create a new limited queue.
99
+ #
100
+ # @parameter limit [Integer] The maximum number of items that can be enqueued.
101
+ # @parameter full [Notification] The notification to use for signaling when the queue is full.
102
+ def initialize(limit = 1, full: Notification.new, **options)
67
103
  super(**options)
68
104
 
69
105
  @limit = limit
70
-
71
- @full = Notification.new
106
+ @full = full
72
107
  end
73
108
 
109
+ # @attribute [Integer] The maximum number of items that can be enqueued.
74
110
  attr :limit
75
111
 
76
112
  # @returns [Boolean] Whether trying to enqueue an item would block.
@@ -78,6 +114,11 @@ module Async
78
114
  @items.size >= @limit
79
115
  end
80
116
 
117
+ # Add an item to the queue.
118
+ #
119
+ # If the queue is full, this method will block until there is space available.
120
+ #
121
+ # @parameter item [Object] The item to add to the queue.
81
122
  def <<(item)
82
123
  while limited?
83
124
  @full.wait
@@ -86,7 +127,12 @@ module Async
86
127
  super
87
128
  end
88
129
 
89
- def enqueue *items
130
+ # Add multiple items to the queue.
131
+ #
132
+ # If the queue is full, this method will block until there is space available.
133
+ #
134
+ # @parameter items [Array] The items to add to the queue.
135
+ def enqueue(*items)
90
136
  while !items.empty?
91
137
  while limited?
92
138
  @full.wait
@@ -95,10 +141,15 @@ module Async
95
141
  available = @limit - @items.size
96
142
  @items.concat(items.shift(available))
97
143
 
98
- self.signal unless self.empty?
144
+ @available.signal unless self.empty?
99
145
  end
100
146
  end
101
147
 
148
+ # Remove and return the next item from the queue.
149
+ #
150
+ # If the queue is empty, this method will block until an item is available.
151
+ #
152
+ # @returns [Object] The next item in the queue.
102
153
  def dequeue
103
154
  item = super
104
155
 
data/lib/async/reactor.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2022, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2018, by Sokolov Yura.
7
7
 
@@ -15,12 +15,14 @@ module Async
15
15
  Async(...)
16
16
  end
17
17
 
18
+ # Initialize the reactor and assign it to the current Fiber scheduler.
18
19
  def initialize(...)
19
20
  super
20
21
 
21
22
  Fiber.set_scheduler(self)
22
23
  end
23
24
 
25
+ # Close the reactor and remove it from the current Fiber scheduler.
24
26
  def scheduler_close
25
27
  self.close
26
28
  end