10
10
require_relative "notification"
11
11
12
12
module Async
13
- # 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.
14
17
#
15
18
# It has a compatible interface with {Notification} and {Condition}, except that it's multi-value.
16
19
#
@@ -21,53 +24,42 @@ class Queue
21
24
class ClosedError < RuntimeError
22
25
end
23
26
24
- # Create a new queue.
27
+ # Create a new thread-safe queue.
25
28
#
26
29
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
27
- # @parameter available [Notification] The notification to use for signaling when items are available.
28
- def initialize ( parent : nil , available : Notification . new )
29
- @items = [ ]
30
- @closed = false
30
+ # @parameter available [Notification] The notification to use for signaling when items are available. (ignored, for compatibility)
31
+ def initialize ( parent : nil , available : nil , queue : Thread ::Queue . new )
32
+ @queue = queue
31
33
@parent = parent
32
- @available = available
33
34
end
34
35
35
36
# @returns [Boolean] Whether the queue is closed.
36
37
def closed?
37
- @closed
38
+ @queue . closed?
38
39
end
39
40
40
41
# Close the queue, causing all waiting tasks to return `nil`. Any subsequent calls to {enqueue} will raise an exception.
41
42
def close
42
- @closed = true
43
-
44
- while @available . waiting?
45
- @available . signal ( nil )
46
- end
43
+ @queue . close
47
44
end
48
45
49
- # @attribute [Array] The items in the queue.
50
- attr :items
51
-
52
46
# @returns [Integer] The number of items in the queue.
53
47
def size
54
- @items . size
48
+ @queue . size
55
49
end
56
50
57
51
# @returns [Boolean] Whether the queue is empty.
58
52
def empty?
59
- @items . empty?
53
+ @queue . empty?
60
54
end
61
55
62
56
# Add an item to the queue.
63
57
def push ( item )
64
- if @closed
58
+ if @queue . closed?
65
59
raise ClosedError , "Cannot push items to a closed queue."
66
60
end
67
61
68
- @items << item
69
-
70
- @available . signal unless self . empty?
62
+ @queue . push ( item )
71
63
end
72
64
73
65
# Compatibility with {::Queue#push}.
@@ -77,26 +69,30 @@ def <<(item)
77
69
78
70
# Add multiple items to the queue.
79
71
def enqueue ( *items )
80
- if @closed
72
+ if @queue . closed?
81
73
raise ClosedError , "Cannot enqueue items to a closed queue."
82
74
end
83
75
84
- @items . concat ( items )
85
-
86
- @available . signal unless self . empty?
76
+ items . each { |item | @queue . push ( item ) }
87
77
end
88
78
89
79
# Remove and return the next item from the queue.
90
80
def dequeue
91
- while @items . empty?
92
- if @closed
93
- return nil
94
- end
81
+ return nil if @queue . closed? && @queue . empty?
82
+
83
+ begin
84
+ # Try non-blocking first
85
+ @queue . pop ( true )
86
+ rescue ThreadError
87
+ # Queue is empty, check if closed
88
+ return nil if @queue . closed?
95
89
96
- @available . wait
90
+ # Use blocking pop - the fiber scheduler will handle this properly
91
+ # in Ruby's fiber scheduler implementation
92
+ @queue . pop ( false )
97
93
end
98
-
99
- @items . shift
94
+ rescue ClosedQueueError
95
+ nil
100
96
end
101
97
102
98
# Compatibility with {::Queue#pop}.
@@ -136,7 +132,7 @@ def wait
136
132
end
137
133
end
138
134
139
- # A queue which limits the number of items that can be enqueued.
135
+ # A thread-safe queue which limits the number of items that can be enqueued.
140
136
# @public Since *Async v1*.
141
137
class LimitedQueue < Queue
142
138
# @private This exists purely for emitting a warning.
@@ -149,30 +145,19 @@ def self.new(...)
149
145
# Create a new limited queue.
150
146
#
151
147
# @parameter limit [Integer] The maximum number of items that can be enqueued.
152
- # @parameter full [Notification] The notification to use for signaling when the queue is full.
153
- def initialize ( limit = 1 , full : Notification . new , **options )
154
- super ( **options )
155
-
156
- @limit = limit
157
- @full = full
148
+ # @parameter full [Notification] The notification to use for signaling when the queue is full. (ignored, for compatibility)
149
+ def initialize ( limit = 1 , full : nil , **options )
150
+ super ( **options , queue : Thread ::SizedQueue . new ( limit ) )
158
151
end
159
152
160
153
# @attribute [Integer] The maximum number of items that can be enqueued.
161
- attr :limit
162
-
163
- # Close the queue, causing all waiting tasks to return `nil`. Any subsequent calls to {enqueue} will raise an exception.
164
- # Also signals all tasks waiting for the queue to be full.
165
- def close
166
- super
167
-
168
- while @full . waiting?
169
- @full . signal ( nil )
170
- end
154
+ def limit
155
+ @queue . max
171
156
end
172
157
173
158
# @returns [Boolean] Whether trying to enqueue an item would block.
174
159
def limited?
175
- !@closed && @items . size >= @limit
160
+ !@queue . closed? && @queue . size >= @queue . max
176
161
end
177
162
178
163
# Add an item to the queue.
@@ -181,46 +166,24 @@ def limited?
181
166
#
182
167
# @parameter item [Object] The item to add to the queue.
183
168
def push ( item )
184
- while limited ?
185
- @full . wait
169
+ if @queue . closed ?
170
+ raise ClosedError , "Cannot push items to a closed queue."
186
171
end
187
172
188
- super
173
+ begin
174
+ @queue . push ( item ) # This will block if queue is full
175
+ rescue ClosedQueueError
176
+ raise ClosedError , "Cannot push items to a closed queue."
177
+ end
189
178
end
190
179
191
180
# Add multiple items to the queue.
192
181
#
193
- # If the queue is full, this method will block until there is space available.
182
+ # If the queue is full, this method will block until there is space available.
194
183
#
195
184
# @parameter items [Array] The items to add to the queue.
196
185
def enqueue ( *items )
197
- while !items . empty?
198
- while limited?
199
- @full . wait
200
- end
201
-
202
- if @closed
203
- raise ClosedError , "Cannot enqueue items to a closed queue."
204
- end
205
-
206
- available = @limit - @items . size
207
- @items . concat ( items . shift ( available ) )
208
-
209
- @available . signal unless self . empty?
210
- end
211
- end
212
-
213
- # Remove and return the next item from the queue.
214
- #
215
- # If the queue is empty, this method will block until an item is available.
216
- #
217
- # @returns [Object] The next item in the queue.
218
- def dequeue
219
- item = super
220
-
221
- @full . signal
222
-
223
- return item
186
+ items . each { |item | push ( item ) }
224
187
end
225
188
end
226
189
end
0 commit comments