From 428d8d127afac11bcf9ef267a898f8a031916746 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Thu, 22 Jun 2023 12:41:31 -0700 Subject: [PATCH 1/3] Audit pass on Sendable conformances and requirements --- Package.swift | 9 +++- .../Buffer/BoundedBufferStateMachine.swift | 5 +- .../Buffer/UnboundedBufferStateMachine.swift | 5 +- .../Channels/AsyncChannel.swift | 2 +- .../Channels/AsyncThrowingChannel.swift | 2 +- .../Channels/ChannelStateMachine.swift | 46 ++++++++++++++++--- .../Channels/ChannelStorage.swift | 2 +- 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/Package.swift b/Package.swift index a1562d76..0574ed4a 100644 --- a/Package.swift +++ b/Package.swift @@ -16,11 +16,16 @@ let package = Package( .library(name: "_CAsyncSequenceValidationSupport", type: .static, targets: ["AsyncSequenceValidation"]), .library(name: "AsyncAlgorithms_XCTest", targets: ["AsyncAlgorithms_XCTest"]), ], - dependencies: [.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.3"))], + dependencies: [.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.4"))], targets: [ .target( name: "AsyncAlgorithms", - dependencies: [.product(name: "Collections", package: "swift-collections")] + dependencies: [.product(name: "Collections", package: "swift-collections"),], + swiftSettings: [ + .unsafeFlags([ + "-strict-concurrency=complete" + ], .when(configuration: .debug)) + ] ), .target( name: "AsyncSequenceValidation", diff --git a/Sources/AsyncAlgorithms/Buffer/BoundedBufferStateMachine.swift b/Sources/AsyncAlgorithms/Buffer/BoundedBufferStateMachine.swift index a5c50a61..b863bc50 100644 --- a/Sources/AsyncAlgorithms/Buffer/BoundedBufferStateMachine.swift +++ b/Sources/AsyncAlgorithms/Buffer/BoundedBufferStateMachine.swift @@ -16,7 +16,7 @@ struct BoundedBufferStateMachine { typealias SuspendedProducer = UnsafeContinuation typealias SuspendedConsumer = UnsafeContinuation?, Never> - private enum State { + fileprivate enum State { case initial(base: Base) case buffering( task: Task, @@ -308,3 +308,6 @@ struct BoundedBufferStateMachine { } } } + +extension BoundedBufferStateMachine: Sendable where Base: Sendable { } +extension BoundedBufferStateMachine.State: Sendable where Base: Sendable { } diff --git a/Sources/AsyncAlgorithms/Buffer/UnboundedBufferStateMachine.swift b/Sources/AsyncAlgorithms/Buffer/UnboundedBufferStateMachine.swift index b163619a..a43c2023 100644 --- a/Sources/AsyncAlgorithms/Buffer/UnboundedBufferStateMachine.swift +++ b/Sources/AsyncAlgorithms/Buffer/UnboundedBufferStateMachine.swift @@ -21,7 +21,7 @@ struct UnboundedBufferStateMachine { case bufferingOldest(Int) } - private enum State { + fileprivate enum State { case initial(base: Base) case buffering( task: Task, @@ -248,3 +248,6 @@ struct UnboundedBufferStateMachine { } } } + +extension UnboundedBufferStateMachine: Sendable where Base: Sendable { } +extension UnboundedBufferStateMachine.State: Sendable where Base: Sendable { } diff --git a/Sources/AsyncAlgorithms/Channels/AsyncChannel.swift b/Sources/AsyncAlgorithms/Channels/AsyncChannel.swift index 75becf2d..8035d06a 100644 --- a/Sources/AsyncAlgorithms/Channels/AsyncChannel.swift +++ b/Sources/AsyncAlgorithms/Channels/AsyncChannel.swift @@ -19,7 +19,7 @@ /// on the `Iterator` is made, or when `finish()` is called from another Task. /// As `finish()` induces a terminal state, there is no more need for a back pressure management. /// This function does not suspend and will finish all the pending iterations. -public final class AsyncChannel: AsyncSequence, @unchecked Sendable { +public final class AsyncChannel: AsyncSequence, @unchecked Sendable { public typealias Element = Element public typealias AsyncIterator = Iterator diff --git a/Sources/AsyncAlgorithms/Channels/AsyncThrowingChannel.swift b/Sources/AsyncAlgorithms/Channels/AsyncThrowingChannel.swift index 28de36ae..2fc48dfe 100644 --- a/Sources/AsyncAlgorithms/Channels/AsyncThrowingChannel.swift +++ b/Sources/AsyncAlgorithms/Channels/AsyncThrowingChannel.swift @@ -18,7 +18,7 @@ /// and is resumed when the next call to `next()` on the `Iterator` is made, or when `finish()`/`fail(_:)` is called /// from another Task. As `finish()` and `fail(_:)` induce a terminal state, there is no more need for a back pressure management. /// Those functions do not suspend and will finish all the pending iterations. -public final class AsyncThrowingChannel: AsyncSequence, @unchecked Sendable { +public final class AsyncThrowingChannel: AsyncSequence, @unchecked Sendable { public typealias Element = Element public typealias AsyncIterator = Iterator diff --git a/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift b/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift index 2972c754..84b5ebc1 100644 --- a/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift +++ b/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift @@ -10,8 +10,42 @@ //===----------------------------------------------------------------------===// @_implementationOnly import OrderedCollections -struct ChannelStateMachine: Sendable { - private struct SuspendedProducer: Hashable { +struct OrderedSetContainer { + var contents: OrderedSet + + var isEmpty: Bool { contents.isEmpty } + + mutating func removeFirst() -> Element { + contents.removeFirst() + } + + mutating func remove(_ element: Element) -> Element? { + contents.remove(element) + } + + @discardableResult + mutating func append(_ element: Element) -> (inserted: Bool, index: Int) { + contents.append(element) + } + + func map(_ apply: (Element) throws -> T) rethrows -> [T] { + try contents.map(apply) + } +} + +extension OrderedSetContainer: ExpressibleByArrayLiteral { + typealias ArrayLiteralElement = OrderedSet.ArrayLiteralElement + + init(arrayLiteral elements: ArrayLiteralElement...) { + contents = OrderedSet(elements) + } +} + +extension OrderedSetContainer: @unchecked Sendable where Element: Sendable { } + + +struct ChannelStateMachine: Sendable { + private struct SuspendedProducer: Hashable, Sendable { let id: UInt64 let continuation: UnsafeContinuation? let element: Element? @@ -29,7 +63,7 @@ struct ChannelStateMachine: Sendable { } } - private struct SuspendedConsumer: Hashable { + private struct SuspendedConsumer: Hashable, Sendable { let id: UInt64 let continuation: UnsafeContinuation? @@ -51,11 +85,11 @@ struct ChannelStateMachine: Sendable { case failed(Error) } - private enum State { + private enum State: Sendable { case channeling( - suspendedProducers: OrderedSet, + suspendedProducers: OrderedSetContainer, cancelledProducers: Set, - suspendedConsumers: OrderedSet, + suspendedConsumers: OrderedSetContainer, cancelledConsumers: Set ) case terminated(Termination) diff --git a/Sources/AsyncAlgorithms/Channels/ChannelStorage.swift b/Sources/AsyncAlgorithms/Channels/ChannelStorage.swift index da398dbc..12b5ba72 100644 --- a/Sources/AsyncAlgorithms/Channels/ChannelStorage.swift +++ b/Sources/AsyncAlgorithms/Channels/ChannelStorage.swift @@ -8,7 +8,7 @@ // See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// -struct ChannelStorage: Sendable { +struct ChannelStorage: Sendable { private let stateMachine: ManagedCriticalState> private let ids = ManagedCriticalState(0) From 37fd4d5aaba0310d361ee5fe0b5335af6ac763d3 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Fri, 23 Jun 2023 11:30:46 -0700 Subject: [PATCH 2/3] A slightly cleaner mark for Sendability of OrderedSet --- .../Channels/ChannelStateMachine.swift | 39 ++----------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift b/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift index 84b5ebc1..e823e5f7 100644 --- a/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift +++ b/Sources/AsyncAlgorithms/Channels/ChannelStateMachine.swift @@ -10,39 +10,8 @@ //===----------------------------------------------------------------------===// @_implementationOnly import OrderedCollections -struct OrderedSetContainer { - var contents: OrderedSet - - var isEmpty: Bool { contents.isEmpty } - - mutating func removeFirst() -> Element { - contents.removeFirst() - } - - mutating func remove(_ element: Element) -> Element? { - contents.remove(element) - } - - @discardableResult - mutating func append(_ element: Element) -> (inserted: Bool, index: Int) { - contents.append(element) - } - - func map(_ apply: (Element) throws -> T) rethrows -> [T] { - try contents.map(apply) - } -} - -extension OrderedSetContainer: ExpressibleByArrayLiteral { - typealias ArrayLiteralElement = OrderedSet.ArrayLiteralElement - - init(arrayLiteral elements: ArrayLiteralElement...) { - contents = OrderedSet(elements) - } -} - -extension OrderedSetContainer: @unchecked Sendable where Element: Sendable { } - +// NOTE: this is only marked as unchecked since the swift-collections tag is before auditing for Sendable +extension OrderedSet: @unchecked Sendable where Element: Sendable { } struct ChannelStateMachine: Sendable { private struct SuspendedProducer: Hashable, Sendable { @@ -87,9 +56,9 @@ struct ChannelStateMachine: Sendable { private enum State: Sendable { case channeling( - suspendedProducers: OrderedSetContainer, + suspendedProducers: OrderedSet, cancelledProducers: Set, - suspendedConsumers: OrderedSetContainer, + suspendedConsumers: OrderedSet, cancelledConsumers: Set ) case terminated(Termination) From 4ac3ee7ae0ecdbb18d28eae3460eb216e1e6ba3f Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Fri, 23 Jun 2023 11:32:01 -0700 Subject: [PATCH 3/3] Remove flags for strict mode --- Package.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index 0574ed4a..56417e84 100644 --- a/Package.swift +++ b/Package.swift @@ -20,12 +20,7 @@ let package = Package( targets: [ .target( name: "AsyncAlgorithms", - dependencies: [.product(name: "Collections", package: "swift-collections"),], - swiftSettings: [ - .unsafeFlags([ - "-strict-concurrency=complete" - ], .when(configuration: .debug)) - ] + dependencies: [.product(name: "Collections", package: "swift-collections")] ), .target( name: "AsyncSequenceValidation",