@@ -132,38 +132,116 @@ extension RequestBody {
132
132
>
133
133
134
134
/// Delegate for NIOThrowingAsyncSequenceProducer
135
+ ///
136
+ /// This can be a struct as the state is stored inside a NIOLockedValueBox which
137
+ /// turns it into a reference value
135
138
@usableFromInline
136
- final class Delegate : NIOAsyncSequenceProducerDelegate , Sendable {
137
- let checkedContinuations : NIOLockedValueBox < Deque < CheckedContinuation < Void , Never > > >
139
+ struct Delegate : NIOAsyncSequenceProducerDelegate , Sendable {
140
+ enum State {
141
+ case produceMore
142
+ case waitingForProduceMore( CheckedContinuation < Void , Never > ? )
143
+ case multipleWaitingForProduceMore( Deque < CheckedContinuation < Void , Never > > )
144
+ case terminated
145
+ }
146
+ let state : NIOLockedValueBox < State >
138
147
139
148
@usableFromInline
140
149
init ( ) {
141
- self . checkedContinuations = . init( [ ] )
150
+ self . state = . init( . produceMore )
142
151
}
143
152
144
153
@usableFromInline
145
154
func produceMore( ) {
146
- self . checkedContinuations. withLockedValue {
147
- if let cont = $0. popFirst ( ) {
148
- cont. resume ( )
155
+ self . state. withLockedValue { state in
156
+ switch state {
157
+ case . produceMore:
158
+ break
159
+ case . waitingForProduceMore( let continuation) :
160
+ if let continuation {
161
+ continuation. resume ( )
162
+ }
163
+ state = . produceMore
164
+
165
+ case . multipleWaitingForProduceMore( var continuations) :
166
+ // this isnt exactly correct as the number of continuations
167
+ // resumed can overflow the back pressure
168
+ while let cont = continuations. popFirst ( ) {
169
+ cont. resume ( )
170
+ }
171
+ state = . produceMore
172
+
173
+ case . terminated:
174
+ preconditionFailure ( " Unexpected state " )
149
175
}
150
176
}
151
177
}
152
178
153
179
@usableFromInline
154
180
func didTerminate( ) {
155
- self . checkedContinuations. withLockedValue {
156
- while let cont = $0. popFirst ( ) {
157
- cont. resume ( )
181
+ self . state. withLockedValue { state in
182
+ switch state {
183
+ case . produceMore:
184
+ break
185
+ case . waitingForProduceMore( let continuation) :
186
+ if let continuation {
187
+ continuation. resume ( )
188
+ }
189
+ state = . terminated
190
+ case . multipleWaitingForProduceMore( var continuations) :
191
+ while let cont = continuations. popFirst ( ) {
192
+ cont. resume ( )
193
+ }
194
+ state = . terminated
195
+ case . terminated:
196
+ preconditionFailure ( " Unexpected state " )
158
197
}
159
198
}
160
199
}
161
200
162
201
@usableFromInline
163
202
func waitForProduceMore( ) async {
164
- await withCheckedContinuation { ( cont: CheckedContinuation < Void , Never > ) in
165
- self . checkedContinuations. withLockedValue {
166
- $0. append ( cont)
203
+ switch self . state. withLockedValue ( { $0 } ) {
204
+ case . produceMore, . terminated:
205
+ break
206
+ case . waitingForProduceMore, . multipleWaitingForProduceMore:
207
+ await withCheckedContinuation { ( newContinuation: CheckedContinuation < Void , Never > ) in
208
+ self . state. withLockedValue { state in
209
+ switch state {
210
+ case . produceMore:
211
+ newContinuation. resume ( )
212
+ case . waitingForProduceMore( let firstContinuation) :
213
+ if let firstContinuation {
214
+ var continuations = Deque < CheckedContinuation < Void , Never > > ( )
215
+ continuations. reserveCapacity ( 2 )
216
+ continuations. append ( firstContinuation)
217
+ continuations. append ( newContinuation)
218
+ state = . multipleWaitingForProduceMore( continuations)
219
+ } else {
220
+ state = . waitingForProduceMore( newContinuation)
221
+ }
222
+ case . multipleWaitingForProduceMore( var continuations) :
223
+ continuations. append ( newContinuation)
224
+ state = . multipleWaitingForProduceMore( continuations)
225
+ case . terminated:
226
+ newContinuation. resume ( )
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ @usableFromInline
234
+ func stopProducing( ) {
235
+ self . state. withLockedValue { state in
236
+ switch state {
237
+ case . produceMore:
238
+ state = . waitingForProduceMore( nil )
239
+ case . waitingForProduceMore:
240
+ break
241
+ case . multipleWaitingForProduceMore:
242
+ break
243
+ case . terminated:
244
+ break
167
245
}
168
246
}
169
247
}
@@ -175,14 +253,11 @@ extension RequestBody {
175
253
let source : Producer . Source
176
254
@usableFromInline
177
255
let delegate : Delegate
178
- @usableFromInline
179
- let waitForProduceMore : NIOLockedValueBox < Bool >
180
256
181
257
@usableFromInline
182
258
init ( source: Producer . Source , delegate: Delegate ) {
183
259
self . source = source
184
260
self . delegate = delegate
185
- self . waitForProduceMore = . init( false )
186
261
}
187
262
188
263
/// Yields the element to the inbound stream.
@@ -195,13 +270,10 @@ extension RequestBody {
195
270
public func yield( _ element: ByteBuffer ) async throws {
196
271
// if previous call indicated we should stop producing wait until the delegate
197
272
// says we can start producing again
198
- if self . waitForProduceMore. withLockedValue ( { $0 } ) {
199
- await self . delegate. waitForProduceMore ( )
200
- self . waitForProduceMore. withLockedValue { $0 = false }
201
- }
273
+ await self . delegate. waitForProduceMore ( )
202
274
let result = self . source. yield ( element)
203
275
if result == . stopProducing {
204
- self . waitForProduceMore . withLockedValue { $0 = true }
276
+ self . delegate . stopProducing ( )
205
277
}
206
278
}
207
279
0 commit comments