Skip to content

Commit 08b3b4f

Browse files
authored
Make _NIOFileSystem strict concurrency compatible (apple#3098)
Motivation: We're continuing out Strict Concurrency journey, making sure users of NIO can write data-race-free code. Modifications: - Added some missing Sendable annotations in NIOAsyncSequenceProducer - Made BufferedStream unconditionally Sendable, and required its Element type to also be Sendable. The prior constraint wasn't actually correct. We always behaved as though the element types were Sendable, by passing them into continuations. This cleans things up. - Made AnyAsyncSequence Sendable, which it needs to be. - Made BufferedOrAnyStream Sendable, which it needs to be. - Made DirectoryEntries explicitly Sendable, which it was. - Made DirectoryEntries.Batched explicitly Sendable. Result: Better concurrency-safety
1 parent 2f1c780 commit 08b3b4f

File tree

5 files changed

+20
-18
lines changed

5 files changed

+20
-18
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ let package = Package(
247247
path: "Sources/NIOFileSystem",
248248
exclude: includePrivacyManifest ? [] : ["PrivacyInfo.xcprivacy"],
249249
resources: includePrivacyManifest ? [.copy("PrivacyInfo.xcprivacy")] : [],
250-
swiftSettings: [
250+
swiftSettings: strictConcurrencySettings + [
251251
.define("ENABLE_MOCKING", .when(configuration: .debug))
252252
]
253253
),

Sources/NIOCore/AsyncSequences/NIOAsyncSequenceProducer.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public struct NIOAsyncSequenceProducer<
105105
/// to yield new elements to the sequence.
106106
/// 2. The ``sequence`` which is the actual `AsyncSequence` and
107107
/// should be passed to the consumer.
108-
public struct NewSequence {
108+
public struct NewSequence: Sendable {
109109
/// The source of the ``NIOAsyncSequenceProducer`` used to yield and finish.
110110
public let source: Source
111111
/// The actual sequence which should be passed to the consumer.
@@ -268,7 +268,7 @@ extension NIOAsyncSequenceProducer {
268268
}
269269

270270
/// The result of a call to ``NIOAsyncSequenceProducer/Source/yield(_:)``.
271-
public enum YieldResult: Hashable {
271+
public enum YieldResult: Hashable, Sendable {
272272
/// Indicates that the caller should produce more elements for now. The delegate's ``NIOAsyncSequenceProducerDelegate/produceMore()``
273273
/// will **NOT** get called, since the demand was already signalled through this ``NIOAsyncSequenceProducer/Source/YieldResult``.
274274
case produceMore

Sources/NIOFileSystem/DirectoryEntries.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import SystemPackage
2121

2222
/// An `AsyncSequence` of entries in a directory.
2323
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
24-
public struct DirectoryEntries: AsyncSequence {
24+
public struct DirectoryEntries: AsyncSequence, Sendable {
2525
public typealias AsyncIterator = DirectoryIterator
2626
public typealias Element = DirectoryEntry
2727

@@ -35,7 +35,8 @@ public struct DirectoryEntries: AsyncSequence {
3535

3636
/// Creates a ``DirectoryEntries`` sequence by wrapping an `AsyncSequence` of _batches_ of
3737
/// directory entries.
38-
public init<S: AsyncSequence>(wrapping sequence: S) where S.Element == Batched.Element {
38+
@preconcurrency
39+
public init<S: AsyncSequence & Sendable>(wrapping sequence: S) where S.Element == Batched.Element {
3940
self.batchedSequence = Batched(wrapping: sequence)
4041
}
4142

@@ -85,15 +86,16 @@ extension DirectoryEntries {
8586
/// The ``Batched`` sequence uses `Array<DirectoryEntry>` as its element type rather
8687
/// than `DirectoryEntry`. This can enable better performance by reducing the number of
8788
/// executor hops at the cost of ease-of-use.
88-
public struct Batched: AsyncSequence {
89+
public struct Batched: AsyncSequence, Sendable {
8990
public typealias AsyncIterator = BatchedIterator
9091
public typealias Element = [DirectoryEntry]
9192

9293
private let stream: BufferedOrAnyStream<[DirectoryEntry], DirectoryEntryProducer>
9394

9495
/// Creates a ``DirectoryEntries/Batched`` sequence by wrapping an `AsyncSequence`
9596
/// of directory entry batches.
96-
public init<S: AsyncSequence>(wrapping sequence: S) where S.Element == Element {
97+
@preconcurrency
98+
public init<S: AsyncSequence & Sendable>(wrapping sequence: S) where S.Element == Element {
9799
self.stream = BufferedOrAnyStream<[DirectoryEntry], DirectoryEntryProducer>(wrapping: sequence)
98100
}
99101

Sources/NIOFileSystem/Internal/BufferedOrAnyStream.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import NIOCore
1616

1717
/// Wraps a ``NIOThrowingAsyncSequenceProducer<Element>`` or ``AnyAsyncSequence<Element>``.
1818
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
19-
internal enum BufferedOrAnyStream<Element, Delegate: NIOAsyncSequenceProducerDelegate> {
19+
internal enum BufferedOrAnyStream<Element: Sendable, Delegate: NIOAsyncSequenceProducerDelegate>: Sendable {
2020
typealias AsyncSequenceProducer = NIOThrowingAsyncSequenceProducer<
2121
Element, Error, NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark, Delegate
2222
>
@@ -28,7 +28,7 @@ internal enum BufferedOrAnyStream<Element, Delegate: NIOAsyncSequenceProducerDel
2828
self = .nioThrowingAsyncSequenceProducer(stream)
2929
}
3030

31-
internal init<S: AsyncSequence>(wrapping stream: S) where S.Element == Element {
31+
internal init<S: AsyncSequence & Sendable>(wrapping stream: S) where S.Element == Element {
3232
self = .anyAsyncSequence(AnyAsyncSequence(wrapping: stream))
3333
}
3434

@@ -69,10 +69,10 @@ internal enum BufferedOrAnyStream<Element, Delegate: NIOAsyncSequenceProducerDel
6969
}
7070

7171
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
72-
internal struct AnyAsyncSequence<Element>: AsyncSequence {
73-
private let _makeAsyncIterator: () -> AsyncIterator
72+
internal struct AnyAsyncSequence<Element>: AsyncSequence, Sendable {
73+
private let _makeAsyncIterator: @Sendable () -> AsyncIterator
7474

75-
internal init<S: AsyncSequence>(wrapping sequence: S) where S.Element == Element {
75+
internal init<S: AsyncSequence & Sendable>(wrapping sequence: S) where S.Element == Element {
7676
self._makeAsyncIterator = {
7777
AsyncIterator(wrapping: sequence.makeAsyncIterator())
7878
}

Sources/NIOFileSystem/Internal/BufferedStream.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ import NIOConcurrencyHelpers
118118
/// }
119119
///
120120
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
121-
internal struct BufferedStream<Element> {
121+
internal struct BufferedStream<Element: Sendable> {
122122
final class _Backing: Sendable {
123123
let storage: _BackPressuredStorage
124124

@@ -199,7 +199,7 @@ extension BufferedStream: AsyncSequence {
199199
}
200200

201201
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
202-
extension BufferedStream: Sendable where Element: Sendable {}
202+
extension BufferedStream: Sendable {}
203203

204204
internal struct _ManagedCriticalState<State>: @unchecked Sendable {
205205
let lock: NIOLockedValueBox<State>
@@ -303,7 +303,7 @@ extension BufferedStream {
303303
/// - Parameter sequence: The elements to write to the asynchronous stream.
304304
/// - Returns: The result that indicates if more elements should be produced at this time.
305305
internal func write<S>(contentsOf sequence: S) throws -> WriteResult
306-
where Element == S.Element, S: Sequence {
306+
where Element == S.Element, S: Sequence, Element: Sendable {
307307
try self._backing.storage.write(contentsOf: sequence)
308308
}
309309

@@ -363,7 +363,7 @@ extension BufferedStream {
363363
internal func write<S>(
364364
contentsOf sequence: S,
365365
onProduceMore: @escaping @Sendable (Result<Void, Error>) -> Void
366-
) where Element == S.Element, S: Sequence {
366+
) where Element == S.Element, S: Sequence, Element: Sendable {
367367
do {
368368
let writeResult = try self.write(contentsOf: sequence)
369369

@@ -407,7 +407,7 @@ extension BufferedStream {
407407
/// - Parameters:
408408
/// - sequence: The elements to write to the asynchronous stream.
409409
internal func write<S>(contentsOf sequence: S) async throws
410-
where Element == S.Element, S: Sequence {
410+
where Element == S.Element, S: Sequence, Element: Sendable {
411411
let writeResult = try { try self.write(contentsOf: sequence) }()
412412

413413
switch writeResult {
@@ -458,7 +458,7 @@ extension BufferedStream {
458458
/// - Parameters:
459459
/// - sequence: The elements to write to the asynchronous stream.
460460
internal func write<S>(contentsOf sequence: S) async throws
461-
where Element == S.Element, S: AsyncSequence {
461+
where Element == S.Element, S: AsyncSequence, Element: Sendable {
462462
for try await element in sequence {
463463
try await self.write(contentsOf: CollectionOfOne(element))
464464
}

0 commit comments

Comments
 (0)