16
16
import CNIODarwin
17
17
import CNIOLinux
18
18
import NIOConcurrencyHelpers
19
- import NIOCore
20
19
import NIOPosix
21
20
@preconcurrency import SystemPackage
22
21
@@ -90,17 +89,17 @@ extension DirectoryEntries {
90
89
public typealias AsyncIterator = BatchedIterator
91
90
public typealias Element = [ DirectoryEntry ]
92
91
93
- private let stream : BufferedOrAnyStream < [ DirectoryEntry ] , DirectoryEntryProducer >
92
+ private let stream : BufferedOrAnyStream < [ DirectoryEntry ] >
94
93
95
94
/// Creates a ``DirectoryEntries/Batched`` sequence by wrapping an `AsyncSequence`
96
95
/// of directory entry batches.
97
96
public init < S: AsyncSequence > ( wrapping sequence: S ) where S. Element == Element {
98
- self . stream = BufferedOrAnyStream < [ DirectoryEntry ] , DirectoryEntryProducer > ( wrapping: sequence)
97
+ self . stream = BufferedOrAnyStream ( wrapping: sequence)
99
98
}
100
99
101
100
fileprivate init ( handle: SystemFileHandle , recursive: Bool ) {
102
101
// Expanding the batches yields watermarks of 256 and 512 directory entries.
103
- let stream = NIOThrowingAsyncSequenceProducer . makeBatchedDirectoryEntryStream (
102
+ let stream = BufferedStream . makeBatchedDirectoryEntryStream (
104
103
handle: handle,
105
104
recursive: recursive,
106
105
entriesPerBatch: 64 ,
@@ -117,11 +116,9 @@ extension DirectoryEntries {
117
116
118
117
/// An `AsyncIteratorProtocol` of `Array<DirectoryEntry>`.
119
118
public struct BatchedIterator : AsyncIteratorProtocol {
120
- private var iterator : BufferedOrAnyStream < [ DirectoryEntry ] , DirectoryEntryProducer > . AsyncIterator
119
+ private var iterator : BufferedOrAnyStream < [ DirectoryEntry ] > . AsyncIterator
121
120
122
- fileprivate init (
123
- wrapping iterator: BufferedOrAnyStream < [ DirectoryEntry ] , DirectoryEntryProducer > . AsyncIterator
124
- ) {
121
+ init ( wrapping iterator: BufferedOrAnyStream < [ DirectoryEntry ] > . AsyncIterator ) {
125
122
self . iterator = iterator
126
123
}
127
124
@@ -138,95 +135,52 @@ extension DirectoryEntries.Batched.AsyncIterator: Sendable {}
138
135
// MARK: - Internal
139
136
140
137
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
141
- extension NIOThrowingAsyncSequenceProducer
142
- where
143
- Element == [ DirectoryEntry ] ,
144
- Failure == ( any Error ) ,
145
- Strategy == NIOAsyncSequenceProducerBackPressureStrategies . HighLowWatermark ,
146
- Delegate == DirectoryEntryProducer
147
- {
138
+ extension BufferedStream where Element == [ DirectoryEntry ] {
148
139
fileprivate static func makeBatchedDirectoryEntryStream(
149
140
handle: SystemFileHandle ,
150
141
recursive: Bool ,
151
142
entriesPerBatch: Int ,
152
143
lowWatermark: Int ,
153
144
highWatermark: Int
154
- ) -> NIOThrowingAsyncSequenceProducer <
155
- [ DirectoryEntry ] , any Error , NIOAsyncSequenceProducerBackPressureStrategies . HighLowWatermark ,
156
- DirectoryEntryProducer
157
- > {
158
- let producer = DirectoryEntryProducer (
159
- handle: handle,
160
- recursive: recursive,
161
- entriesPerBatch: entriesPerBatch
162
- )
145
+ ) -> BufferedStream < [ DirectoryEntry ] > {
146
+ let state = DirectoryEnumerator ( handle: handle, recursive: recursive)
147
+ let protectedState = NIOLockedValueBox ( state)
163
148
164
- let nioThrowingAsyncSequence = NIOThrowingAsyncSequenceProducer . makeSequence (
165
- elementType: [ DirectoryEntry ] . self,
166
- backPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies . HighLowWatermark (
167
- lowWatermark: lowWatermark,
168
- highWatermark: highWatermark
169
- ) ,
170
- finishOnDeinit: false ,
171
- delegate: producer
149
+ var ( stream, source) = BufferedStream . makeStream (
150
+ of: [ DirectoryEntry ] . self,
151
+ backPressureStrategy: . watermark( low: lowWatermark, high: highWatermark)
172
152
)
173
153
174
- producer. setSequenceProducerSource ( nioThrowingAsyncSequence. source)
154
+ source. onTermination = {
155
+ guard let threadPool = protectedState. withLockedValue ( { $0. threadPoolForClosing ( ) } ) else {
156
+ return
157
+ }
158
+
159
+ threadPool. submit { _ in // always run, even if cancelled
160
+ protectedState. withLockedValue { state in
161
+ state. closeIfNecessary ( )
162
+ }
163
+ }
164
+ }
165
+
166
+ let producer = DirectoryEntryProducer (
167
+ state: protectedState,
168
+ source: source,
169
+ entriesPerBatch: entriesPerBatch
170
+ )
171
+ // Start producing immediately.
172
+ producer. produceMore ( )
175
173
176
- return nioThrowingAsyncSequence . sequence
174
+ return stream
177
175
}
178
176
}
179
177
180
178
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
181
- private typealias DirectoryEntrySequenceProducer = NIOThrowingAsyncSequenceProducer <
182
- [ DirectoryEntry ] , Error , NIOAsyncSequenceProducerBackPressureStrategies . HighLowWatermark , DirectoryEntryProducer
183
- >
184
-
185
- @available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
186
- private final class DirectoryEntryProducer : NIOAsyncSequenceProducerDelegate {
179
+ private struct DirectoryEntryProducer {
187
180
let state : NIOLockedValueBox < DirectoryEnumerator >
181
+ let source : BufferedStream < [ DirectoryEntry ] > . Source
188
182
let entriesPerBatch : Int
189
183
190
- init ( handle: SystemFileHandle , recursive: Bool , entriesPerBatch: Int ) {
191
- let state = DirectoryEnumerator ( handle: handle, recursive: recursive)
192
- self . state = NIOLockedValueBox ( state)
193
- self . entriesPerBatch = entriesPerBatch
194
- }
195
-
196
- func didTerminate( ) {
197
- guard let threadPool = self . state. withLockedValue ( { $0. threadPoolForClosing ( ) } ) else {
198
- return
199
- }
200
-
201
- threadPool. submit { _ in // always run, even if cancelled
202
- self . state. withLockedValue { state in
203
- state. closeIfNecessary ( )
204
- }
205
- }
206
- }
207
-
208
- /// sets the source within the producer state
209
- func setSequenceProducerSource( _ sequenceProducerSource: DirectoryEntrySequenceProducer . Source ) {
210
- self . state. withLockedValue { state in
211
- switch state. state {
212
- case . idle:
213
- state. sequenceProducerSource = sequenceProducerSource
214
- case . done:
215
- sequenceProducerSource. finish ( )
216
- case . open, . openPausedProducing:
217
- fatalError ( )
218
- case . modifying:
219
- fatalError ( )
220
- }
221
- }
222
- }
223
-
224
- func clearSource( ) {
225
- self . state. withLockedValue { state in
226
- state. sequenceProducerSource = nil
227
- }
228
- }
229
-
230
184
/// The 'entry point' for producing elements.
231
185
///
232
186
/// Calling this function will start producing directory entries asynchronously by dispatching
@@ -253,12 +207,6 @@ private final class DirectoryEntryProducer: NIOAsyncSequenceProducerDelegate {
253
207
}
254
208
}
255
209
256
- func pauseProducing( ) {
257
- self . state. withLockedValue { state in
258
- state. pauseProducing ( )
259
- }
260
- }
261
-
262
210
private func nextBatch( ) throws -> [ DirectoryEntry ] {
263
211
try self . state. withLockedValue { state in
264
212
try state. next ( self . entriesPerBatch)
@@ -273,51 +221,45 @@ private final class DirectoryEntryProducer: NIOAsyncSequenceProducerDelegate {
273
221
// Failed to read more entries: close and notify the stream so consumers receive the
274
222
// error.
275
223
self . close ( )
276
- let source = self . state. withLockedValue { state in
277
- state. sequenceProducerSource
278
- }
279
- source? . finish ( error)
280
- self . clearSource ( )
224
+ self . source. finish ( throwing: error)
281
225
}
282
226
}
283
227
284
228
private func onNextBatch( _ entries: [ DirectoryEntry ] ) {
285
- let source = self . state. withLockedValue { state in
286
- state. sequenceProducerSource
287
- }
288
-
289
- guard let source else {
290
- assertionFailure ( " unexpectedly missing source " )
291
- return
292
- }
293
-
294
229
// No entries were read: this must be the end (as the batch size must be greater than zero).
295
230
if entries. isEmpty {
296
- source. finish ( )
297
- self . clearSource ( )
231
+ self . source. finish ( throwing: nil )
298
232
return
299
233
}
300
234
301
235
// Reading short means reading EOF. The enumerator closes itself in that case.
302
236
let readEOF = entries. count < self . entriesPerBatch
303
237
304
238
// Entries were produced: yield them and maybe produce more.
305
- let writeResult = source. yield ( contentsOf: CollectionOfOne ( entries) )
306
-
307
- // Exit early if EOF was read; no use in trying to produce more.
308
- if readEOF {
309
- source. finish ( )
310
- self . clearSource ( )
311
- return
312
- }
239
+ do {
240
+ let writeResult = try self . source. write ( contentsOf: CollectionOfOne ( entries) )
241
+ // Exit early if EOF was read; no use in trying to produce more.
242
+ if readEOF {
243
+ self . source. finish ( throwing: nil )
244
+ return
245
+ }
313
246
314
- switch writeResult {
315
- case . produceMore:
316
- self . produceMore ( )
317
- case . stopProducing:
318
- self . pauseProducing ( )
319
- case . dropped:
320
- // The source is finished; mark ourselves as done.
247
+ switch writeResult {
248
+ case . produceMore:
249
+ self . produceMore ( )
250
+ case let . enqueueCallback( token) :
251
+ self . source. enqueueCallback ( callbackToken: token) {
252
+ switch $0 {
253
+ case . success:
254
+ self . produceMore ( )
255
+ case . failure:
256
+ self . close ( )
257
+ }
258
+ }
259
+ }
260
+ } catch {
261
+ // Failure to write means the source is already done, that's okay we just need to
262
+ // update our state and stop producing.
321
263
self . close ( )
322
264
}
323
265
}
@@ -340,30 +282,25 @@ private final class DirectoryEntryProducer: NIOAsyncSequenceProducerDelegate {
340
282
/// Note that this is not a `Sequence` because we allow for errors to be thrown on `next()`.
341
283
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
342
284
private struct DirectoryEnumerator : Sendable {
343
- internal enum State : @unchecked Sendable {
285
+ private enum State : @unchecked Sendable {
344
286
case modifying
345
287
case idle( SystemFileHandle . SendableView , recursive: Bool )
346
288
case open( NIOThreadPool , Source , [ DirectoryEntry ] )
347
- case openPausedProducing( NIOThreadPool , Source , [ DirectoryEntry ] )
348
289
case done
349
290
}
350
291
351
292
/// The source of directory entries.
352
- internal enum Source {
293
+ private enum Source {
353
294
case readdir( CInterop . DirPointer )
354
295
case fts( CInterop . FTSPointer )
355
296
}
356
297
357
298
/// The current state of enumeration.
358
- internal var state : State
299
+ private var state : State
359
300
360
301
/// The path to the directory being enumerated.
361
302
private let path : FilePath
362
303
363
- /// The route via which directory entry batches are yielded,
364
- /// the sourcing end of the `DirectoryEntrySequenceProducer`
365
- internal var sequenceProducerSource : DirectoryEntrySequenceProducer . Source ?
366
-
367
304
/// Information about an entry returned by FTS. See 'fts(3)'.
368
305
private enum FTSInfo : Hashable , Sendable {
369
306
case directoryPreOrder
@@ -416,38 +353,22 @@ private struct DirectoryEnumerator: Sendable {
416
353
self . path = handle. path
417
354
}
418
355
419
- internal mutating func produceMore( ) -> NIOThreadPool ? {
356
+ internal func produceMore( ) -> NIOThreadPool ? {
420
357
switch self . state {
421
358
case let . idle( handle, _) :
422
359
return handle. threadPool
423
360
case let . open( threadPool, _, _) :
424
361
return threadPool
425
- case . openPausedProducing( let threadPool, let source, let array) :
426
- self . state = . open( threadPool, source, array)
427
- return threadPool
428
362
case . done:
429
363
return nil
430
364
case . modifying:
431
365
fatalError ( )
432
366
}
433
367
}
434
368
435
- internal mutating func pauseProducing( ) {
436
- switch self . state {
437
- case . open( let threadPool, let source, let array) :
438
- self . state = . openPausedProducing( threadPool, source, array)
439
- case . idle:
440
- ( ) // we won't apply back pressure until something has been read
441
- case . openPausedProducing, . done:
442
- ( ) // no-op
443
- case . modifying:
444
- fatalError ( )
445
- }
446
- }
447
-
448
369
internal func threadPoolForClosing( ) -> NIOThreadPool ? {
449
370
switch self . state {
450
- case . open ( let threadPool , _ , _ ) , . openPausedProducing ( let threadPool, _, _) :
371
+ case let . open ( threadPool, _, _) :
451
372
return threadPool
452
373
case . idle, . done:
453
374
// Don't need to close in the idle state: we don't own the handle.
@@ -476,7 +397,7 @@ private struct DirectoryEnumerator: Sendable {
476
397
// We don't own the handle so don't close it.
477
398
self . state = . done
478
399
479
- case . open ( _ , let mode , _ ) , . openPausedProducing ( _, let mode, _) :
400
+ case let . open ( _, mode, _) :
480
401
self . state = . done
481
402
switch mode {
482
403
case . readdir( let dir) :
@@ -710,9 +631,6 @@ private struct DirectoryEnumerator: Sendable {
710
631
return result
711
632
}
712
633
713
- case . openPausedProducing:
714
- return . yield( . success( [ ] ) )
715
-
716
634
case . done:
717
635
return . yield( . success( [ ] ) )
718
636
0 commit comments