async 2.12.1 → 2.14.0

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: 7074249673c5f7127018a3c609ed127c962c748090959f59a2c3306bf7972a52
4
+ data.tar.gz: f3c1149ee1e5483d62a4b19ebb6c6a52fe66b0b27151685f0d939b02727c5768
5
5
  SHA512:
6
- metadata.gz: 30176358a5c9efc3df3e525e0ba941f5771bb7619bef28e1b6229a547ad3a24cb5475483a257704c3eea2d9214dcca2e17932994e0859a1c5d4dfc6401b24d48
7
- data.tar.gz: 8b7353cf2ef0884c78a919775b84280d3234c7868c6c3b95aa00ae560a5825a07bd9533eda1afcc9776d1c94b7cc116e549e24144e4bab7c9bb3f086782a7456
6
+ metadata.gz: 2ea1c73288b49d8f6f5eaeae711b8dc56cd93758dedca4711c52d8caea940eeaaccf423307a3633d7ae2c2317111a1120a143e88ebe15ed57063ec489973214f
7
+ data.tar.gz: a2f2d68e6fac9168d91401f907f633f99a6a4f9e323007f72d5bfa6884f1e3b30968ee8bc8a7602c36351c0f3d3b7f28ab922297c41e92a56cd8cd9009956991
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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,6 +33,9 @@ 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
data/lib/async/list.rb CHANGED
@@ -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
@@ -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
@@ -73,7 +74,7 @@ module Async
73
74
  end
74
75
  end
75
76
 
76
- # @returns [Node] the root node in the hierarchy.
77
+ # @returns [Node] The root node in the hierarchy.
77
78
  def root
78
79
  @parent&.root || self
79
80
  end
@@ -87,10 +88,10 @@ module Async
87
88
  # @attribute [Node] The parent node.
88
89
  attr :parent
89
90
 
90
- # @attribute children [Children | Nil] Optional list of children.
91
+ # @attribute [Children | Nil] Optional list of children.
91
92
  attr :children
92
93
 
93
- # A useful identifier for the current node.
94
+ # @attribute [String | Nil] A useful identifier for the current node.
94
95
  attr :annotation
95
96
 
96
97
  # Whether this node has any children.
@@ -109,6 +110,9 @@ module Async
109
110
  @transient
110
111
  end
111
112
 
113
+ # Annotate the node with a description.
114
+ #
115
+ # @parameter annotation [String] The description to annotate the node with.
112
116
  def annotate(annotation)
113
117
  if block_given?
114
118
  begin
@@ -123,6 +127,9 @@ module Async
123
127
  end
124
128
  end
125
129
 
130
+ # A description of the node, including the annotation and object name.
131
+ #
132
+ # @returns [String] The description of the node.
126
133
  def description
127
134
  @object_name ||= "#{self.class}:#{format '%#018x', object_id}#{@transient ? ' transient' : nil}"
128
135
 
@@ -135,10 +142,14 @@ module Async
135
142
  end
136
143
  end
137
144
 
145
+ # Provides a backtrace for nodes that have an active execution context.
146
+ #
147
+ # @returns [Array(Thread::Backtrace::Locations) | Nil] The backtrace of the node, if available.
138
148
  def backtrace(*arguments)
139
149
  nil
140
150
  end
141
151
 
152
+ # @returns [String] A description of the node.
142
153
  def to_s
143
154
  "\#<#{self.description}>"
144
155
  end
@@ -255,10 +266,15 @@ module Async
255
266
  end
256
267
  end
257
268
 
269
+ # Whether the node has been stopped.
258
270
  def stopped?
259
271
  @children.nil?
260
272
  end
261
273
 
274
+ # Print the hierarchy of the task tree from the given node.
275
+ #
276
+ # @parameter out [IO] The output stream to write to.
277
+ # @parameter backtrace [Boolean] Whether to print the backtrace of each node.
262
278
  def print_hierarchy(out = $stdout, backtrace: true)
263
279
  self.traverse do |node, level|
264
280
  indent = "\t" * level
@@ -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
@@ -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
@@ -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
@@ -16,7 +16,11 @@ require 'resolv'
16
16
  module Async
17
17
  # Handles scheduling of fibers. Implements the fiber scheduler interface.
18
18
  class Scheduler < Node
19
+ # Raised when an operation is attempted on a closed scheduler.
19
20
  class ClosedError < RuntimeError
21
+ # Create a new error.
22
+ #
23
+ # @parameter message [String] The error message.
20
24
  def initialize(message = "Scheduler is closed!")
21
25
  super
22
26
  end
@@ -28,6 +32,11 @@ module Async
28
32
  true
29
33
  end
30
34
 
35
+ # Create a new scheduler.
36
+ #
37
+ # @public Since `stable-v1`.
38
+ # @parameter parent [Node | Nil] The parent node to use for task hierarchy.
39
+ # @parameter selector [IO::Event::Selector] The selector to use for event handling.
31
40
  def initialize(parent = nil, selector: nil)
32
41
  super(parent)
33
42
 
@@ -63,6 +72,9 @@ module Async
63
72
  end
64
73
  end
65
74
 
75
+ # Invoked when the fiber scheduler is being closed.
76
+ #
77
+ # Executes the run loop until all tasks are finished, then closes the scheduler.
66
78
  def scheduler_close
67
79
  # If the execution context (thread) was handling an exception, we want to exit as quickly as possible:
68
80
  unless $!
@@ -79,6 +91,7 @@ module Async
79
91
  end
80
92
  end
81
93
 
94
+ # Terminate all child tasks and close the scheduler.
82
95
  # @public Since `stable-v1`.
83
96
  def close
84
97
  # It's critical to stop all tasks. Otherwise they might be holding on to resources which are never closed/released correctly.
@@ -108,6 +121,7 @@ module Async
108
121
  @selector.nil?
109
122
  end
110
123
 
124
+ # @returns [String] A description of the scheduler.
111
125
  def to_s
112
126
  "\#<#{self.description} #{@children&.size || 0} children (#{stopped? ? 'stopped' : 'running'})>"
113
127
  end
@@ -135,10 +149,20 @@ module Async
135
149
  @selector.push(fiber)
136
150
  end
137
151
 
138
- def raise(*arguments)
139
- @selector.raise(*arguments)
152
+ # Raise an exception on a specified fiber with the given arguments.
153
+ #
154
+ # This internally schedules the current fiber to be ready, before raising the exception, so that it will later resume execution.
155
+ #
156
+ # @parameter fiber [Fiber] The fiber to raise the exception on.
157
+ # @parameter *arguments [Array] The arguments to pass to the fiber.
158
+ def raise(...)
159
+ @selector.raise(...)
140
160
  end
141
161
 
162
+ # Resume execution of the specified fiber.
163
+ #
164
+ # @parameter fiber [Fiber] The fiber to resume.
165
+ # @parameter arguments [Array] The arguments to pass to the fiber.
142
166
  def resume(fiber, *arguments)
143
167
  @selector.resume(fiber, *arguments)
144
168
  end
data/lib/async/task.rb CHANGED
@@ -16,15 +16,21 @@ require_relative 'condition'
16
16
  module Async
17
17
  # Raised when a task is explicitly stopped.
18
18
  class Stop < Exception
19
+ # Used to defer stopping the current task until later.
19
20
  class Later
21
+ # Create a new stop later operation.
22
+ #
23
+ # @parameter task [Task] The task to stop later.
20
24
  def initialize(task)
21
25
  @task = task
22
26
  end
23
27
 
28
+ # @returns [Boolean] Whether the task is alive.
24
29
  def alive?
25
30
  true
26
31
  end
27
32
 
33
+ # Transfer control to the operation - this will stop the task.
28
34
  def transfer
29
35
  @task.stop
30
36
  end
@@ -34,6 +40,9 @@ module Async
34
40
  # Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
35
41
  # @public Since `stable-v1`.
36
42
  class TimeoutError < StandardError
43
+ # Create a new timeout error.
44
+ #
45
+ # @parameter message [String] The error message.
37
46
  def initialize(message = "execution expired")
38
47
  super
39
48
  end
@@ -41,7 +50,11 @@ module Async
41
50
 
42
51
  # @public Since `stable-v1`.
43
52
  class Task < Node
53
+ # Raised when a child task is created within a task that has finished execution.
44
54
  class FinishedError < RuntimeError
55
+ # Create a new finished error.
56
+ #
57
+ # @parameter message [String] The error message.
45
58
  def initialize(message = "Cannot create child task within a task that has finished execution!")
46
59
  super
47
60
  end
@@ -72,14 +85,21 @@ module Async
72
85
  @defer_stop = nil
73
86
  end
74
87
 
88
+ # @returns [Scheduler] The scheduler for this task.
75
89
  def reactor
76
90
  self.root
77
91
  end
78
92
 
93
+ # @returns [Array(Thread::Backtrace::Location) | Nil] The backtrace of the task, if available.
79
94
  def backtrace(*arguments)
80
95
  @fiber&.backtrace(*arguments)
81
96
  end
82
97
 
98
+ # Annotate the task with a description.
99
+ #
100
+ # This will internally try to annotate the fiber if it is running, otherwise it will annotate the task itself.
101
+ #
102
+ # @parameter annotation [String] The description to annotate the task with.
83
103
  def annotate(annotation, &block)
84
104
  if @fiber
85
105
  @fiber.annotate(annotation, &block)
@@ -88,6 +108,7 @@ module Async
88
108
  end
89
109
  end
90
110
 
111
+ # @returns [Object] The annotation of the task.
91
112
  def annotation
92
113
  if @fiber
93
114
  @fiber.annotation
@@ -96,6 +117,7 @@ module Async
96
117
  end
97
118
  end
98
119
 
120
+ # @returns [String] A description of the task and it's current status.
99
121
  def to_s
100
122
  "\#<#{self.description} (#{@status})>"
101
123
  end
@@ -115,10 +137,10 @@ module Async
115
137
  Fiber.scheduler.yield
116
138
  end
117
139
 
118
- # @attr fiber [Fiber] The fiber which is being used for the execution of this task.
140
+ # @attribute [Fiber] The fiber which is being used for the execution of this task.
119
141
  attr :fiber
120
142
 
121
- # Whether the internal fiber is alive, i.e. it
143
+ # @returns [Boolean] Whether the internal fiber is alive, i.e. it is actively executing.
122
144
  def alive?
123
145
  @fiber&.alive?
124
146
  end
@@ -130,32 +152,34 @@ module Async
130
152
  super && @block.nil? && @fiber.nil?
131
153
  end
132
154
 
133
- # Whether the task is running.
134
- # @returns [Boolean]
155
+ # @returns [Boolean] Whether the task is running.
135
156
  def running?
136
157
  @status == :running
137
158
  end
138
159
 
160
+ # @returns [Boolean] Whether the task failed with an exception.
139
161
  def failed?
140
162
  @status == :failed
141
163
  end
142
164
 
143
- # The task has been stopped
165
+ # @returns [Boolean] Whether the task has been stopped.
144
166
  def stopped?
145
167
  @status == :stopped
146
168
  end
147
169
 
148
- # The task has completed execution and generated a result.
170
+ # @returns [Boolean] Whether the task has completed execution and generated a result.
149
171
  def completed?
150
172
  @status == :completed
151
173
  end
152
174
 
153
175
  alias complete? completed?
154
176
 
155
- # @attr status [Symbol] The status of the execution of the fiber, one of `:initialized`, `:running`, `:complete`, `:stopped` or `:failed`.
177
+ # @attribute [Symbol] The status of the execution of the fiber, one of `:initialized`, `:running`, `:complete`, `:stopped` or `:failed`.
156
178
  attr :status
157
179
 
158
180
  # Begin the execution of the task.
181
+ #
182
+ # @raises [RuntimeError] If the task is already running.
159
183
  def run(*arguments)
160
184
  if @status == :initialized
161
185
  @status = :running
@@ -169,6 +193,9 @@ module Async
169
193
  end
170
194
 
171
195
  # Run an asynchronous task as a child of the current task.
196
+ #
197
+ # @raises [FinishedError] If the task has already finished.
198
+ # @returns [Task] The child task.
172
199
  def async(*arguments, **options, &block)
173
200
  raise FinishedError if self.finished?
174
201
 
@@ -293,11 +320,12 @@ module Async
293
320
  end
294
321
 
295
322
  # Check if there is a task defined for the current fiber.
296
- # @returns [Task | Nil]
323
+ # @returns [Interface(:async) | Nil]
297
324
  def self.current?
298
325
  Thread.current[:async_task]
299
326
  end
300
327
 
328
+ # @returns [Boolean] Whether this task is the currently executing task.
301
329
  def current?
302
330
  Fiber.current.equal?(@fiber)
303
331
  end
@@ -326,19 +354,14 @@ module Async
326
354
  @status = :completed
327
355
  end
328
356
 
329
- # This is a very tricky aspect of tasks to get right. I've modelled it after `Thread` but it's slightly different in that the exception can propagate back up through the reactor. If the user writes code which raises an exception, that exception should always be visible, i.e. cause a failure. If it's not visible, such code fails silently and can be very difficult to debug.
330
- def failed!(exception = false, propagate = true)
357
+ # State transition into the failed state.
358
+ def failed!(exception = false)
331
359
  @result = exception
332
360
  @status = :failed
333
361
 
334
- if exception
335
- if propagate
336
- raise exception
337
- elsif @finished.nil?
338
- # If no one has called wait, we log this as a warning:
339
- Console::Event::Failure.for(exception).emit(self, "Task may have ended with unhandled exception.", severity: :warn)
340
- else
341
- Console::Event::Failure.for(exception).emit(self, severity: :debug)
362
+ if $DEBUG
363
+ Fiber.blocking do
364
+ $stderr.puts "Task #{self} failed:", exception.full_message
342
365
  end
343
366
  end
344
367
  end
@@ -379,9 +402,12 @@ module Async
379
402
  rescue Stop
380
403
  stopped!
381
404
  rescue StandardError => error
382
- failed!(error, false)
405
+ failed!(error)
383
406
  rescue Exception => exception
384
- failed!(exception, true)
407
+ failed!(exception)
408
+
409
+ # This is a critical failure, we should stop the reactor:
410
+ raise
385
411
  ensure
386
412
  # Console.info(self) {"Task ensure $! = #{$!} with #{@children&.size.inspect} children!"}
387
413
  finish!
@@ -6,12 +6,21 @@
6
6
  require_relative 'condition'
7
7
 
8
8
  module Async
9
+ # A synchronization primitive that allows one task to wait for another task to resolve a value.
9
10
  class Variable
11
+ # Create a new variable.
12
+ #
13
+ # @parameter condition [Condition] The condition to use for synchronization.
10
14
  def initialize(condition = Condition.new)
11
15
  @condition = condition
12
16
  @value = nil
13
17
  end
14
18
 
19
+ # Resolve the value.
20
+ #
21
+ # Signals all waiting tasks.
22
+ #
23
+ # @parameter value [Object] The value to resolve.
15
24
  def resolve(value = true)
16
25
  @value = value
17
26
  condition = @condition
@@ -22,15 +31,22 @@ module Async
22
31
  condition.signal(value)
23
32
  end
24
33
 
34
+ # Whether the value has been resolved.
35
+ #
36
+ # @returns [Boolean] Whether the value has been resolved.
25
37
  def resolved?
26
38
  @condition.nil?
27
39
  end
28
40
 
41
+ # Wait for the value to be resolved.
42
+ #
43
+ # @returns [Object] The resolved value.
29
44
  def value
30
45
  @condition&.wait
31
46
  return @value
32
47
  end
33
48
 
49
+ # Alias for {#value}.
34
50
  def wait
35
51
  self.value
36
52
  end
data/lib/async/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
6
  module Async
7
- VERSION = "2.12.1"
7
+ VERSION = "2.14.0"
8
8
  end
data/lib/async/waiter.rb CHANGED
@@ -6,6 +6,10 @@
6
6
  module Async
7
7
  # A composable synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore} and/or {Barrier}.
8
8
  class Waiter
9
+ # Create a waiter instance.
10
+ #
11
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for asynchronous operations.
12
+ # @parameter finished [Async::Condition] The condition to signal when a task completes.
9
13
  def initialize(parent: nil, finished: Async::Condition.new)
10
14
  @finished = finished
11
15
  @done = []
@@ -15,8 +19,8 @@ module Async
15
19
 
16
20
  # Execute a child task and add it to the waiter.
17
21
  # @asynchronous Executes the given block concurrently.
18
- def async(parent: (@parent or Task.current), &block)
19
- parent.async do |task|
22
+ def async(parent: (@parent or Task.current), **options, &block)
23
+ parent.async(**options) do |task|
20
24
  yield(task)
21
25
  ensure
22
26
  @done << task
data/lib/async/wrapper.rb CHANGED
@@ -23,6 +23,7 @@ module Async
23
23
 
24
24
  attr_accessor :reactor
25
25
 
26
+ # Dup the underlying IO.
26
27
  def dup
27
28
  self.class.new(@io.dup)
28
29
  end
@@ -51,11 +52,12 @@ module Async
51
52
  @io.to_io.wait(::IO::READABLE|::IO::WRITABLE|::IO::PRIORITY, timeout) or raise TimeoutError
52
53
  end
53
54
 
54
- # Close the io and monitor.
55
+ # Close the underlying IO.
55
56
  def close
56
57
  @io.close
57
58
  end
58
59
 
60
+ # Whether the underlying IO is closed.
59
61
  def closed?
60
62
  @io.closed?
61
63
  end
data/lib/async.rb CHANGED
@@ -10,5 +10,6 @@ require_relative "async/reactor"
10
10
  require_relative "kernel/async"
11
11
  require_relative "kernel/sync"
12
12
 
13
+ # Asynchronous programming framework.
13
14
  module Async
14
15
  end
data/readme.md CHANGED
@@ -1,4 +1,4 @@
1
- # ![Async](logo.svg)
1
+ # ![Async](assets/logo.webp)
2
2
 
3
3
  Async is a composable asynchronous I/O framework for Ruby based on [io-event](https://github.com/socketry/io-event).
4
4
 
@@ -54,16 +54,9 @@ This project is governed by the [Contributor Covenant](https://www.contributor-c
54
54
 
55
55
  ## See Also
56
56
 
57
- - [async-io](https://github.com/socketry/async-io) — Asynchronous networking and sockets.
58
57
  - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client/server.
59
- - [async-process](https://github.com/socketry/async-process) — Asynchronous process spawning/waiting.
60
58
  - [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server websockets.
61
59
  - [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server.
62
- - [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs.
63
-
64
- ### Projects Using Async
65
-
66
- - [ciri](https://github.com/ciri-ethereum/ciri) — An Ethereum implementation written in Ruby.
67
60
  - [falcon](https://github.com/socketry/falcon) — A rack compatible server built on top of `async-http`.
68
61
  - [rubydns](https://github.com/ioquatix/rubydns) — An easy to use Ruby DNS server.
69
62
  - [slack-ruby-bot](https://github.com/slack-ruby/slack-ruby-bot) — A client for making slack bots.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.1
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -62,7 +62,7 @@ cert_chain:
62
62
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
63
63
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
64
64
  -----END CERTIFICATE-----
65
- date: 2024-06-23 00:00:00.000000000 Z
65
+ date: 2024-07-14 00:00:00.000000000 Z
66
66
  dependencies:
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: console
metadata.gz.sig CHANGED
Binary file