async 2.12.0 → 2.13.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: 8d13c552e4e34cd2b67661c228e727df5d3c7da697e7f59f369e02af5c76e513
4
- data.tar.gz: 954607e3c71338fb54fc59d627211a722870a5f8f934c84c113735cf24c24195
3
+ metadata.gz: fe1866e547e6705255fdce380d3e5532964d0b74c009579f481f3c3fb8086b6c
4
+ data.tar.gz: 5d3ac4bbbb8579cbcc85944c447dcea2e6b4b2408af9ad3829eace9934067199
5
5
  SHA512:
6
- metadata.gz: 392264144eed819eb93c49e53d2f0de6771cb966314a4e901223138af848fe550be76dd55bcc267db36d2593893e8421193568704987bcaab241171e13003e4c
7
- data.tar.gz: 85d97a0013909d6b1be52f73b49fcf3c7610ac59f43ab3794b0959d0266a2c283ea220fb6549fadb13e03be84abc5683988e15b07e607aefc24cca81eeab2479
6
+ metadata.gz: e208dc1b43979431fe3bf36acdcbebb97306bf32ec8471ea9081763d737865e47486dc798d5704ed829b27b16385f48425c4af687eda169f94e36f1c32c8ec86
7
+ data.tar.gz: d961dafc64289d00b1e99b3245577d5e549365d415e6628cba39af4a5b0d58fc15da468d880fa1796c97351ff83ba2814d073ebbab18aaeaef6a3eb50be26efb
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
@@ -346,6 +370,7 @@ module Async
346
370
  Kernel::raise ClosedError if @selector.nil?
347
371
 
348
372
  initial_task = self.async(...) if block_given?
373
+ interrupt = nil
349
374
 
350
375
  begin
351
376
  # In theory, we could use Exception here to be a little bit safer, but we've only shown the case for SignalException to be a problem, so let's not over-engineer this.
@@ -358,14 +383,17 @@ module Async
358
383
  break unless self.run_once
359
384
  end
360
385
  end
361
- rescue Interrupt
386
+ rescue Interrupt => interrupt
362
387
  Thread.handle_interrupt(::SignalException => :never) do
363
388
  self.stop
364
389
  end
365
390
 
366
391
  retry
367
392
  end
368
-
393
+
394
+ # If the event loop was interrupted, and we finished exiting normally (due to the interrupt), we need to re-raise the interrupt so that the caller can handle it too.
395
+ Kernel.raise interrupt if interrupt
396
+
369
397
  return initial_task
370
398
  ensure
371
399
  Console.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
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
@@ -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.0"
7
+ VERSION = "2.13.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.0
4
+ version: 2.13.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-04 00:00:00.000000000 Z
65
+ date: 2024-07-12 00:00:00.000000000 Z
66
66
  dependencies:
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: console
@@ -105,6 +105,9 @@ dependencies:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
107
  version: '1.6'
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 1.6.5
108
111
  type: :runtime
109
112
  prerelease: false
110
113
  version_requirements: !ruby/object:Gem::Requirement
@@ -112,6 +115,9 @@ dependencies:
112
115
  - - "~>"
113
116
  - !ruby/object:Gem::Version
114
117
  version: '1.6'
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 1.6.5
115
121
  description:
116
122
  email:
117
123
  executables: []
@@ -166,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
172
  - !ruby/object:Gem::Version
167
173
  version: '0'
168
174
  requirements: []
169
- rubygems_version: 3.5.3
175
+ rubygems_version: 3.5.9
170
176
  signing_key:
171
177
  specification_version: 4
172
178
  summary: A concurrency framework for Ruby.
metadata.gz.sig CHANGED
Binary file