async 2.17.0 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/best-practices.md +188 -0
  4. data/context/debugging.md +63 -0
  5. data/context/getting-started.md +177 -0
  6. data/context/index.yaml +29 -0
  7. data/context/scheduler.md +109 -0
  8. data/context/tasks.md +448 -0
  9. data/context/thread-safety.md +651 -0
  10. data/lib/async/barrier.md +1 -2
  11. data/lib/async/barrier.rb +35 -12
  12. data/lib/async/clock.rb +11 -2
  13. data/lib/async/condition.md +1 -1
  14. data/lib/async/condition.rb +18 -34
  15. data/lib/async/console.rb +42 -0
  16. data/lib/async/deadline.rb +70 -0
  17. data/lib/async/idler.rb +2 -1
  18. data/lib/async/limited_queue.rb +13 -0
  19. data/lib/async/list.rb +16 -8
  20. data/lib/async/node.rb +5 -3
  21. data/lib/async/notification.rb +13 -9
  22. data/lib/async/priority_queue.rb +253 -0
  23. data/lib/async/promise.rb +188 -0
  24. data/lib/async/queue.rb +70 -82
  25. data/lib/async/reactor.rb +4 -2
  26. data/lib/async/scheduler.rb +233 -54
  27. data/lib/async/semaphore.rb +3 -3
  28. data/lib/async/stop.rb +82 -0
  29. data/lib/async/task.rb +111 -81
  30. data/lib/async/timeout.rb +88 -0
  31. data/lib/async/variable.rb +15 -4
  32. data/lib/async/version.rb +2 -2
  33. data/lib/async/waiter.rb +6 -1
  34. data/lib/kernel/async.rb +1 -1
  35. data/lib/kernel/sync.rb +14 -5
  36. data/lib/metrics/provider/async/task.rb +20 -0
  37. data/lib/metrics/provider/async.rb +6 -0
  38. data/lib/traces/provider/async/barrier.rb +17 -0
  39. data/lib/traces/provider/async/task.rb +40 -0
  40. data/lib/traces/provider/async.rb +7 -0
  41. data/license.md +8 -1
  42. data/readme.md +50 -7
  43. data/releases.md +357 -0
  44. data.tar.gz.sig +0 -0
  45. metadata +61 -20
  46. metadata.gz.sig +0 -0
  47. data/lib/async/waiter.md +0 -50
  48. data/lib/async/wrapper.rb +0 -65
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Shopify Inc.
5
+ # Copyright, 2025, by Samuel Williams.
6
+
7
+ require "io/event/priority_heap"
8
+ require "thread"
9
+
10
+ require_relative "queue"
11
+
12
+ module Async
13
+ # A queue which allows items to be processed in priority order of consumers.
14
+ #
15
+ # Unlike a traditional priority queue where items have priorities, this queue
16
+ # assigns priorities to consumers (fibers waiting to dequeue). Higher priority
17
+ # consumers are served first when items become available.
18
+ #
19
+ # @public Since *Async v2*.
20
+ class PriorityQueue
21
+ ClosedError = Queue::ClosedError
22
+
23
+ # A waiter represents a fiber waiting to dequeue with a given priority.
24
+ Waiter = Struct.new(:fiber, :priority, :sequence, :condition, :value) do
25
+ include Comparable
26
+
27
+ def <=>(other)
28
+ # Higher priority comes first, then FIFO for equal priorities:
29
+ if priority == other.priority
30
+ # Use sequence for FIFO behavior (lower sequence = earlier):
31
+ sequence <=> other.sequence
32
+ else
33
+ other.priority <=> priority # Reverse for max-heap behavior
34
+ end
35
+ end
36
+
37
+ def signal(value)
38
+ self.value = value
39
+ condition.signal
40
+ end
41
+
42
+ def wait_for_value(mutex, timeout = nil)
43
+ condition.wait(mutex, timeout)
44
+ return self.value
45
+ end
46
+
47
+ # Invalidate this waiter, making it unusable and detectable as abandoned.
48
+ def invalidate!
49
+ self.fiber = nil
50
+ end
51
+
52
+ # Check if this waiter has been invalidated.
53
+ def valid?
54
+ self.fiber&.alive?
55
+ end
56
+ end
57
+
58
+ private_constant :Waiter
59
+
60
+ # Create a new priority queue.
61
+ #
62
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
63
+ def initialize(parent: nil)
64
+ @items = []
65
+ @closed = false
66
+ @parent = parent
67
+ @waiting = IO::Event::PriorityHeap.new
68
+ @sequence = 0
69
+
70
+ @mutex = Mutex.new
71
+ end
72
+
73
+ # Close the queue, causing all waiting tasks to return `nil`.
74
+ # Any subsequent calls to {enqueue} will raise an exception.
75
+ def close
76
+ @mutex.synchronize do
77
+ @closed = true
78
+
79
+ # Signal all waiting fibers with nil, skipping dead/invalid ones:
80
+ while waiter = @waiting.pop
81
+ waiter.signal(nil)
82
+ end
83
+ end
84
+ end
85
+
86
+ # @returns [Boolean] Whether the queue is closed.
87
+ def closed?
88
+ @closed
89
+ end
90
+
91
+ # @attribute [Array] The items in the queue.
92
+ attr :items
93
+
94
+ # @returns [Integer] The number of items in the queue.
95
+ def size
96
+ @items.size
97
+ end
98
+
99
+ # @returns [Boolean] Whether the queue is empty.
100
+ def empty?
101
+ @items.empty?
102
+ end
103
+
104
+ # @returns [Integer] The number of fibers waiting to dequeue.
105
+ def waiting_count
106
+ @mutex.synchronize do
107
+ @waiting.size
108
+ end
109
+ end
110
+
111
+ # @deprecated Use {#waiting_count} instead.
112
+ alias waiting waiting_count
113
+
114
+ # Add an item to the queue.
115
+ #
116
+ # @parameter item [Object] The item to add to the queue.
117
+ def push(item)
118
+ @mutex.synchronize do
119
+ if @closed
120
+ raise ClosedError, "Cannot push items to a closed queue."
121
+ end
122
+
123
+ @items << item
124
+
125
+ # Wake up the highest priority waiter if any, skipping dead/invalid waiters:
126
+ while waiter = @waiting.pop
127
+ if waiter.valid?
128
+ value = @items.shift
129
+ waiter.signal(value)
130
+ break
131
+ end
132
+ # Dead/invalid waiter discarded, try next one.
133
+ end
134
+ end
135
+ end
136
+
137
+ # Compatibility with {::Queue#push}.
138
+ def <<(item)
139
+ self.push(item)
140
+ end
141
+
142
+ # Add multiple items to the queue.
143
+ #
144
+ # @parameter items [Array] The items to add to the queue.
145
+ def enqueue(*items)
146
+ @mutex.synchronize do
147
+ if @closed
148
+ raise ClosedError, "Cannot enqueue items to a closed queue."
149
+ end
150
+
151
+ @items.concat(items)
152
+
153
+ # Wake up waiting fibers in priority order, skipping dead/invalid waiters:
154
+ while !@items.empty? && (waiter = @waiting.pop)
155
+ if waiter.valid?
156
+ value = @items.shift
157
+ waiter.signal(value)
158
+ end
159
+ # Dead/invalid waiter discarded, continue to next one.
160
+ end
161
+ end
162
+ end
163
+
164
+ # Remove and return the next item from the queue.
165
+ #
166
+ # If the queue is empty, this method will block until an item is available or timeout expires.
167
+ # Fibers are served in priority order, with higher priority fibers receiving
168
+ # items first.
169
+ #
170
+ # @parameter priority [Numeric] The priority of this consumer (higher = served first).
171
+ # @parameter timeout [Numeric, nil] Maximum time to wait for an item. If nil, waits indefinitely. If 0, returns immediately.
172
+ # @returns [Object, nil] The next item in the queue, or nil if timeout expires.
173
+ def dequeue(priority: 0, timeout: nil)
174
+ @mutex.synchronize do
175
+ # If queue is closed and empty, return nil immediately:
176
+ if @closed && @items.empty?
177
+ return nil
178
+ end
179
+
180
+ # Fast path: if items available and either no waiters or we have higher priority:
181
+ unless @items.empty?
182
+ head = @waiting.peek
183
+ if head.nil? or priority > head.priority
184
+ return @items.shift
185
+ end
186
+ end
187
+
188
+ # Handle immediate timeout (non-blocking)
189
+ return nil if timeout == 0
190
+
191
+ # Need to wait - create our own condition variable and add to waiting queue:
192
+ sequence = @sequence
193
+ @sequence += 1
194
+
195
+ condition = ConditionVariable.new
196
+
197
+ begin
198
+ waiter = Waiter.new(Fiber.current, priority, sequence, condition, nil)
199
+ @waiting.push(waiter)
200
+
201
+ # Wait for our specific condition variable to be signaled:
202
+ return waiter.wait_for_value(@mutex, timeout)
203
+ ensure
204
+ waiter&.invalidate!
205
+ end
206
+ end
207
+ end
208
+
209
+ # Compatibility with {::Queue#pop}.
210
+ #
211
+ # @parameter priority [Numeric] The priority of this consumer.
212
+ # @parameter timeout [Numeric, nil] Maximum time to wait for an item. If nil, waits indefinitely. If 0, returns immediately.
213
+ # @returns [Object, nil] The dequeued item, or nil if timeout expires.
214
+ def pop(priority: 0, timeout: nil)
215
+ self.dequeue(priority: priority, timeout: timeout)
216
+ end
217
+
218
+ # Process each item in the queue.
219
+ #
220
+ # @asynchronous Executes the given block concurrently for each item.
221
+ #
222
+ # @parameter priority [Numeric] The priority for processing items.
223
+ # @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
224
+ # @parameter options [Hash] The options to pass to the task.
225
+ # @yields {|task| ...} When the system is idle, the block will be executed in a new task.
226
+ def async(priority: 0, parent: (@parent or Task.current), **options, &block)
227
+ while item = self.dequeue(priority: priority)
228
+ parent.async(item, **options, &block)
229
+ end
230
+ end
231
+
232
+ # Enumerate each item in the queue.
233
+ #
234
+ # @parameter priority [Numeric] The priority for dequeuing items.
235
+ def each(priority: 0)
236
+ while item = self.dequeue(priority: priority)
237
+ yield item
238
+ end
239
+ end
240
+
241
+ # Signal the queue with a value, the same as {#enqueue}.
242
+ def signal(value = nil)
243
+ self.enqueue(value)
244
+ end
245
+
246
+ # Wait for an item to be available, the same as {#dequeue}.
247
+ #
248
+ # @parameter priority [Numeric] The priority of this consumer.
249
+ def wait(priority: 0)
250
+ self.dequeue(priority: priority)
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Shopify Inc.
5
+ # Copyright, 2025, by Samuel Williams.
6
+
7
+ module Async
8
+ # A promise represents a value that will be available in the future.
9
+ # Unlike Condition, once resolved (or rejected), all future waits return immediately
10
+ # with the stored value or raise the stored exception.
11
+ #
12
+ # This is thread-safe and integrates with the fiber scheduler.
13
+ #
14
+ # @public Since *Async v2*.
15
+ class Promise
16
+ # Create a new promise.
17
+ def initialize
18
+ # nil = pending, :completed = success, :failed = failure, :cancelled = cancelled:
19
+ @resolved = nil
20
+
21
+ # Stores either the result value or the exception:
22
+ @value = nil
23
+
24
+ # Track how many fibers are currently waiting:
25
+ @waiting = 0
26
+
27
+ @mutex = Mutex.new
28
+ @condition = ConditionVariable.new
29
+ end
30
+
31
+ # @returns [Boolean] Whether the promise has been resolved or rejected.
32
+ def resolved?
33
+ @mutex.synchronize {!!@resolved}
34
+ end
35
+
36
+ # @returns [Symbol | Nil] The internal resolved state (:completed, :failed, :cancelled, or nil if pending).
37
+ # @private For internal use by Task.
38
+ def resolved
39
+ @mutex.synchronize {@resolved}
40
+ end
41
+
42
+ # @returns [Boolean] Whether the promise has been cancelled.
43
+ def cancelled?
44
+ @mutex.synchronize {@resolved == :cancelled}
45
+ end
46
+
47
+ # @returns [Boolean] Whether the promise failed with an exception.
48
+ def failed?
49
+ @mutex.synchronize {@resolved == :failed}
50
+ end
51
+
52
+ # @returns [Boolean] Whether the promise has completed successfully.
53
+ def completed?
54
+ @mutex.synchronize {@resolved == :completed}
55
+ end
56
+
57
+ # @returns [Boolean] Whether any fibers are currently waiting for this promise.
58
+ def waiting?
59
+ @mutex.synchronize {@waiting > 0}
60
+ end
61
+
62
+ # Artificially mark that someone is waiting (useful for suppressing warnings).
63
+ # @private Internal use only.
64
+ def suppress_warnings!
65
+ @mutex.synchronize {@waiting += 1}
66
+ end
67
+
68
+ # Non-blocking access to the current value. Returns nil if not yet resolved.
69
+ # Does not raise exceptions even if the promise was rejected or cancelled.
70
+ # For resolved promises, returns the raw stored value (result, exception, or cancel exception).
71
+ #
72
+ # @returns [Object | Nil] The stored value, or nil if pending.
73
+ def value
74
+ @mutex.synchronize {@resolved ? @value : nil}
75
+ end
76
+
77
+ # Wait for the promise to be resolved and return the value.
78
+ # If already resolved, returns immediately. If rejected, raises the stored exception.
79
+ #
80
+ # @returns [Object] The resolved value.
81
+ # @raises [Exception] The rejected or cancelled exception.
82
+ def wait
83
+ @mutex.synchronize do
84
+ # Increment waiting count:
85
+ @waiting += 1
86
+
87
+ begin
88
+ # Wait for resolution if not already resolved:
89
+ @condition.wait(@mutex) unless @resolved
90
+
91
+ # Return value or raise exception based on resolution type:
92
+ if @resolved == :completed
93
+ return @value
94
+ else
95
+ # Both :failed and :cancelled store exceptions in @value
96
+ raise @value
97
+ end
98
+ ensure
99
+ # Decrement waiting count when done:
100
+ @waiting -= 1
101
+ end
102
+ end
103
+ end
104
+
105
+ # Resolve the promise with a value.
106
+ # All current and future waiters will receive this value.
107
+ # Can only be called once - subsequent calls are ignored.
108
+ #
109
+ # @parameter value [Object] The value to resolve the promise with.
110
+ def resolve(value)
111
+ @mutex.synchronize do
112
+ return if @resolved
113
+
114
+ @value = value
115
+ @resolved = :completed
116
+
117
+ # Wake up all waiting fibers:
118
+ @condition.broadcast
119
+ end
120
+
121
+ return value
122
+ end
123
+
124
+ # Reject the promise with an exception.
125
+ # All current and future waiters will receive this exception.
126
+ # Can only be called once - subsequent calls are ignored.
127
+ #
128
+ # @parameter exception [Exception] The exception to reject the promise with.
129
+ def reject(exception)
130
+ @mutex.synchronize do
131
+ return if @resolved
132
+
133
+ @value = exception
134
+ @resolved = :failed
135
+
136
+ # Wake up all waiting fibers:
137
+ @condition.broadcast
138
+ end
139
+
140
+ return nil
141
+ end
142
+
143
+ # Exception used to indicate cancellation.
144
+ class Cancel < Exception
145
+ end
146
+
147
+ # Cancel the promise, indicating cancellation.
148
+ # All current and future waiters will receive nil.
149
+ # Can only be called on pending promises - no-op if already resolved.
150
+ def cancel(exception = Cancel.new("Promise was cancelled!"))
151
+ @mutex.synchronize do
152
+ # No-op if already in any final state
153
+ return if @resolved
154
+
155
+ @value = exception
156
+ @resolved = :cancelled
157
+
158
+ # Wake up all waiting fibers:
159
+ @condition.broadcast
160
+ end
161
+
162
+ return nil
163
+ end
164
+
165
+ # Resolve the promise with the result of the block.
166
+ # If the block raises an exception, the promise will be rejected.
167
+ # If the promise was already resolved, the block will not be called.
168
+ # @yields {...} The block to call to resolve the promise.
169
+ # @returns [Object] The result of the block.
170
+ def fulfill(&block)
171
+ raise "Promise already resolved!" if @resolved
172
+
173
+ begin
174
+ return self.resolve(yield)
175
+ rescue Cancel => exception
176
+ return self.cancel(exception)
177
+ rescue => error
178
+ return self.reject(error)
179
+ rescue Exception => exception
180
+ self.reject(exception)
181
+ raise
182
+ ensure
183
+ # Handle non-local exits (throw, etc.) that bypass normal flow:
184
+ self.resolve(nil) unless @resolved
185
+ end
186
+ end
187
+ end
188
+ end
data/lib/async/queue.rb CHANGED
@@ -1,70 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2024, by Samuel Williams.
4
+ # Copyright, 2018-2025, by Samuel Williams.
5
5
  # Copyright, 2019, by Ryan Musgrave.
6
6
  # Copyright, 2020-2022, by Bruno Sutic.
7
+ # Copyright, 2025, by Jahfer Husain.
8
+ # Copyright, 2025, by Shopify Inc.
7
9
 
8
- require_relative 'notification'
10
+ require_relative "notification"
9
11
 
10
12
  module Async
11
- # A queue which allows items to be processed in order.
13
+ # A thread-safe queue which allows items to be processed in order.
14
+ #
15
+ # This implementation uses Thread::Queue internally for thread safety while
16
+ # maintaining compatibility with the fiber scheduler.
12
17
  #
13
18
  # It has a compatible interface with {Notification} and {Condition}, except that it's multi-value.
14
19
  #
15
- # @public Since `stable-v1`.
20
+ # @asynchronous This class is thread-safe.
21
+ # @public Since *Async v1*.
16
22
  class Queue
17
- # Create a new queue.
23
+ # An error raised when trying to enqueue items to a closed queue.
24
+ # @public Since *Async v2.24*.
25
+ class ClosedError < RuntimeError
26
+ end
27
+
28
+ # Create a new thread-safe queue.
18
29
  #
19
30
  # @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)
22
- @items = []
31
+ def initialize(parent: nil, delegate: Thread::Queue.new)
32
+ @delegate = delegate
23
33
  @parent = parent
24
- @available = available
25
34
  end
26
35
 
27
- # @attribute [Array] The items in the queue.
28
- attr :items
36
+ # @returns [Boolean] Whether the queue is closed.
37
+ def closed?
38
+ @delegate.closed?
39
+ end
40
+
41
+ # Close the queue, causing all waiting tasks to return `nil`. Any subsequent calls to {enqueue} will raise an exception.
42
+ def close
43
+ @delegate.close
44
+ end
29
45
 
30
46
  # @returns [Integer] The number of items in the queue.
31
47
  def size
32
- @items.size
48
+ @delegate.size
33
49
  end
34
50
 
35
51
  # @returns [Boolean] Whether the queue is empty.
36
52
  def empty?
37
- @items.empty?
53
+ @delegate.empty?
54
+ end
55
+
56
+ # @returns [Integer] The number of tasks waiting for an item.
57
+ def waiting_count
58
+ @delegate.num_waiting
38
59
  end
39
60
 
40
61
  # Add an item to the queue.
41
62
  def push(item)
42
- @items << item
43
-
44
- @available.signal unless self.empty?
63
+ @delegate.push(item)
64
+ rescue ClosedQueueError
65
+ raise ClosedError, "Cannot enqueue items to a closed queue!"
45
66
  end
46
67
 
47
68
  # Compatibility with {::Queue#push}.
48
- alias << push
69
+ def <<(item)
70
+ self.push(item)
71
+ end
49
72
 
50
73
  # Add multiple items to the queue.
51
74
  def enqueue(*items)
52
- @items.concat(items)
53
-
54
- @available.signal unless self.empty?
75
+ items.each {|item| @delegate.push(item)}
76
+ rescue ClosedQueueError
77
+ raise ClosedError, "Cannot enqueue items to a closed queue!"
55
78
  end
56
79
 
57
80
  # Remove and return the next item from the queue.
58
- def dequeue
59
- while @items.empty?
60
- @available.wait
61
- end
62
-
63
- @items.shift
81
+ # @parameter timeout [Numeric, nil] Maximum time to wait for an item. If nil, waits indefinitely. If 0, returns immediately.
82
+ # @returns [Object, nil] The dequeued item, or nil if timeout expires.
83
+ def dequeue(timeout: nil)
84
+ @delegate.pop(timeout: timeout)
64
85
  end
65
86
 
66
87
  # Compatibility with {::Queue#pop}.
67
- alias pop dequeue
88
+ # @parameter timeout [Numeric, nil] Maximum time to wait for an item. If nil, waits indefinitely. If 0, returns immediately.
89
+ # @returns [Object, nil] The dequeued item, or nil if timeout expires.
90
+ def pop(timeout: nil)
91
+ @delegate.pop(timeout: timeout)
92
+ end
68
93
 
69
94
  # Process each item in the queue.
70
95
  #
@@ -88,7 +113,7 @@ module Async
88
113
  end
89
114
 
90
115
  # Signal the queue with a value, the same as {#enqueue}.
91
- def signal(value)
116
+ def signal(value = nil)
92
117
  self.enqueue(value)
93
118
  end
94
119
 
@@ -98,70 +123,33 @@ module Async
98
123
  end
99
124
  end
100
125
 
101
- # A queue which limits the number of items that can be enqueued.
102
- # @public Since `stable-v1`.
126
+ # A thread-safe queue which limits the number of items that can be enqueued.
127
+ #
128
+ # @public Since *Async v1*.
103
129
  class LimitedQueue < Queue
130
+ # @private This exists purely for emitting a warning.
131
+ def self.new(...)
132
+ warn("`require 'async/limited_queue'` to use `Async::LimitedQueue`.", uplevel: 1, category: :deprecated) if $VERBOSE
133
+
134
+ super
135
+ end
136
+
104
137
  # Create a new limited queue.
105
138
  #
106
139
  # @parameter limit [Integer] The maximum number of items that can be enqueued.
107
- # @parameter full [Notification] The notification to use for signaling when the queue is full.
108
- def initialize(limit = 1, full: Notification.new, **options)
109
- super(**options)
110
-
111
- @limit = limit
112
- @full = full
140
+ # @parameter full [Notification] The notification to use for signaling when the queue is full. (ignored, for compatibility)
141
+ def initialize(limit = 1, **options)
142
+ super(**options, delegate: Thread::SizedQueue.new(limit))
113
143
  end
114
144
 
115
145
  # @attribute [Integer] The maximum number of items that can be enqueued.
116
- attr :limit
146
+ def limit
147
+ @delegate.max
148
+ end
117
149
 
118
150
  # @returns [Boolean] Whether trying to enqueue an item would block.
119
151
  def limited?
120
- @items.size >= @limit
121
- end
122
-
123
- # Add an item to the queue.
124
- #
125
- # If the queue is full, this method will block until there is space available.
126
- #
127
- # @parameter item [Object] The item to add to the queue.
128
- def <<(item)
129
- while limited?
130
- @full.wait
131
- end
132
-
133
- super
134
- end
135
-
136
- # Add multiple items to the queue.
137
- #
138
- # If the queue is full, this method will block until there is space available.
139
- #
140
- # @parameter items [Array] The items to add to the queue.
141
- def enqueue(*items)
142
- while !items.empty?
143
- while limited?
144
- @full.wait
145
- end
146
-
147
- available = @limit - @items.size
148
- @items.concat(items.shift(available))
149
-
150
- @available.signal unless self.empty?
151
- end
152
- end
153
-
154
- # Remove and return the next item from the queue.
155
- #
156
- # If the queue is empty, this method will block until an item is available.
157
- #
158
- # @returns [Object] The next item in the queue.
159
- def dequeue
160
- item = super
161
-
162
- @full.signal
163
-
164
- return item
152
+ !@delegate.closed? && @delegate.size >= @delegate.max
165
153
  end
166
154
  end
167
155
  end
data/lib/async/reactor.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2025, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2018, by Sokolov Yura.
7
7
 
8
- require_relative 'scheduler'
8
+ require_relative "scheduler"
9
9
 
10
10
  module Async
11
11
  # A wrapper around the the scheduler which binds it to the current thread automatically.
12
12
  class Reactor < Scheduler
13
13
  # @deprecated Replaced by {Kernel::Async}.
14
14
  def self.run(...)
15
+ warn("`Async::Reactor.run{}` is deprecated, use `Async{}` instead.", uplevel: 1, category: :deprecated) if $VERBOSE
16
+
15
17
  Async(...)
16
18
  end
17
19