Skip to content

Commit

Permalink
feat: Make models Sendable (#829)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbelkins authored Sep 24, 2024
1 parent a001f88 commit ea842e8
Show file tree
Hide file tree
Showing 28 changed files with 347 additions and 343 deletions.
15 changes: 0 additions & 15 deletions Sources/ClientRuntime/Networking/CRTError+Error.swift

This file was deleted.

22 changes: 19 additions & 3 deletions Sources/ClientRuntime/PrimitiveTypeExtensions/Indirect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,28 @@
// SPDX-License-Identifier: Apache-2.0
//

import class Foundation.NSRecursiveLock

@propertyWrapper
public class Indirect<T> {
public var wrappedValue: Optional<T>
public final class Indirect<T: Sendable>: @unchecked Sendable {
private let lock = NSRecursiveLock()
private var _wrappedValue: Optional<T>

public var wrappedValue: Optional<T> {
get {
lock.lock()
defer { lock.unlock() }
return _wrappedValue
}
set {
lock.lock()
defer { lock.unlock() }
_wrappedValue = newValue
}
}

public init(wrappedValue: Optional<T>) {
self.wrappedValue = wrappedValue
self._wrappedValue = wrappedValue
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Smithy/ByteStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import class Foundation.FileHandle
import struct Foundation.Data

public enum ByteStream {
public enum ByteStream: Sendable {
case data(Data?)
case stream(Stream)
case noStream
Expand Down
17 changes: 16 additions & 1 deletion Sources/Smithy/Document/SmithyDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import struct Foundation.Data
import struct Foundation.Date

public protocol SmithyDocument {
public protocol SmithyDocument: Sendable {

/// The Smithy type corresponding to the data stored in this document.
var type: ShapeType { get }

// "as" methods throw if the document doesn't match the requested type.
Expand Down Expand Up @@ -116,6 +117,20 @@ public extension SmithyDocument {

extension SmithyDocument {

/// Compares two `SmithyDocument`-conforming values, checking for equality.
///
/// Two `SmithyDocument`s are equal if they have the same type and equal values.
///
/// Two Smithy `list` documents are equal if they have equal documents at every index.
///
/// Two Smithy `map` documents are equal if they have the same set of keys, and equal values for every key.
///
/// - note: Because `SmithyDocument` is a protocol, it cannot conform to `Equatable`; the type-erased
/// container type ``Document`` is used to provide Smithy documents with equatability.
/// - Parameters:
/// - lhs: The first `SmithyDocument` to compare.
/// - rhs: The second `SmithyDocument` to compare.
/// - Returns: `true` if the two `SmithyDocument`s are equal, `false` otherwise.
public static func isEqual(_ lhs: SmithyDocument, _ rhs: SmithyDocument) -> Bool {
switch (lhs.type, rhs.type) {
case (.blob, .blob):
Expand Down
4 changes: 2 additions & 2 deletions Sources/Smithy/Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import struct Foundation.Data

/// Protocol that provides reading data from a stream
public protocol ReadableStream: AnyObject {
public protocol ReadableStream: AnyObject, Sendable {
/// Returns the current position in the stream
var position: Data.Index { get }

Expand Down Expand Up @@ -45,7 +45,7 @@ public protocol ReadableStream: AnyObject {
}

/// Protocol that provides writing data to a stream
public protocol WriteableStream: AnyObject {
public protocol WriteableStream: AnyObject, Sendable {
/// Writes the contents of `data` to the stream
/// - Parameter data: data to write
func write(contentsOf data: Data) throws
Expand Down
4 changes: 2 additions & 2 deletions Sources/SmithyChecksums/ChunkedStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import class SmithyStreams.BufferedStream
/// URLSessionHTTPClient streams chunked payloads using this stream type.
/// CRTClientEngine uses only the reader provided by this type to create chunks, then it
/// streams them itself.
public class ChunkedStream {
private var inputStream: Stream
public class ChunkedStream: @unchecked Sendable {
private let inputStream: Stream
private var signingConfig: SigningConfig
private var previousSignature: String
private var trailingHeaders: Headers
Expand Down
2 changes: 1 addition & 1 deletion Sources/SmithyChecksums/ValidatingBufferedStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import struct Foundation.Data
import AwsCommonRuntimeKit
import class SmithyStreams.BufferedStream

public class ValidatingBufferedStream {
public class ValidatingBufferedStream: @unchecked Sendable {
private var stream: BufferedStream
private var checksumAlgorithm: ChecksumAlgorithm
private var checksum: (any Checksum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SmithyEventStreamsAuthAPI
import struct Foundation.Data

/// Stream adapter that encodes input into `Data` objects.
public class DefaultMessageEncoderStream<Event>: MessageEncoderStream, Stream {
public class DefaultMessageEncoderStream<Event>: MessageEncoderStream, Stream, @unchecked Sendable {

let stream: AsyncThrowingStream<Event, Error>
let messageEncoder: MessageEncoder
Expand Down
8 changes: 0 additions & 8 deletions Sources/SmithyEventStreamsAPI/EventStreamError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,3 @@ public enum EventStreamError: Error {
/// This may be due to missing required headers
case invalidMessage(String)
}

extension AsyncThrowingStream: Equatable where Element: Equatable {

public static func == (lhs: AsyncThrowingStream, rhs: AsyncThrowingStream) -> Bool {
// TODO: Remove as part of https://github.com/awslabs/aws-sdk-swift/issues/898
return false
}
}
4 changes: 2 additions & 2 deletions Sources/SmithyHTTPAPI/Headers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0
//

public struct Headers {
public struct Headers: Sendable {
public var headers: [Header] = []

/// Creates an empty instance.
Expand Down Expand Up @@ -197,7 +197,7 @@ extension Array where Element == Header {
}
}

public struct Header {
public struct Header: Sendable {
public var name: String
public var value: [String]

Expand Down
2 changes: 1 addition & 1 deletion Sources/SmithyStreams/BufferedStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import protocol Smithy.Stream
/// Note: This class is thread-safe and async-safe.
/// Note: if data is not read from the stream, the buffer will grow indefinitely until the stream is closed.
/// or reach the maximum size of a `Data` object.
public class BufferedStream: Stream {
public class BufferedStream: Stream, @unchecked Sendable {

/// Returns the cumulative length of all data so far written to the stream, if known.
/// For a buffered stream, the length will only be known if the stream has closed.
Expand Down
4 changes: 2 additions & 2 deletions Sources/SmithyStreams/FileStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import protocol Smithy.Stream
/// A `Stream` that wraps a `FileHandle` for reading the file.
///
/// - Note: This class is thread-safe.
public final class FileStream: Stream {
public final class FileStream: Stream, @unchecked Sendable {

/// Returns the length of the stream, if known
public var length: Int? {
Expand All @@ -26,7 +26,7 @@ public final class FileStream: Stream {
let fileHandle: FileHandle

/// Returns the current position of the stream.
public var position: Data.Index
public private(set) var position: Data.Index

/// Returns true if length is zero, false otherwise.
public var isEmpty: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@ class EnumGenerator(
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(null, shape)
writer.openBlock(
"public enum \$enum.name:L: \$N, \$N, \$N, \$N {",
"public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"}",
SwiftTypes.Protocols.Sendable,
SwiftTypes.Protocols.Equatable,
SwiftTypes.Protocols.RawRepresentable,
SwiftTypes.Protocols.CaseIterable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class IntEnumGenerator(
if (isNestedType) {
val service = model.expectShape<ServiceShape>(settings.service)
writer.openBlock("extension ${service.nestedNamespaceType(symbolProvider)} {", "}") {
writer.write("")
renderEnum()
}
} else {
Expand All @@ -41,8 +42,9 @@ class IntEnumGenerator(
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(null, shape)
writer.openBlock(
"public enum \$enum.name:L: \$N, \$N, \$N, \$N {",
"public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"}",
SwiftTypes.Protocols.Sendable,
SwiftTypes.Protocols.Equatable,
SwiftTypes.Protocols.RawRepresentable,
SwiftTypes.Protocols.CaseIterable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ class StructureGenerator(
}

private fun generateStruct() {
writer.write("")
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(model, shape)
val equatableConformance = writer.format(": \$N ", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait<EquatableConformanceTrait>() } ?: ""
writer.openBlock("public struct \$struct.name:L $equatableConformance{")
val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait<EquatableConformanceTrait>() } ?: ""
writer.openBlock("public struct \$struct.name:L: \$N$equatableConformance {", SwiftTypes.Protocols.Sendable)
.call { generateStructMembers() }
.write("")
.call { generateInitializerForStructure(false) }
.closeBlock("}")
.write("")
}

private fun generateStructMembers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class UnionGenerator(
if (isNestedType) {
val service = model.expectShape<ServiceShape>(settings.service)
writer.openBlock("extension ${service.nestedNamespaceType(symbolProvider)} {", "}") {
writer.write("")
renderUnion()
}
} else {
Expand All @@ -76,8 +77,8 @@ class UnionGenerator(
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(model, shape)
val indirectOrNot = "indirect ".takeIf { shape.hasTrait<RecursiveUnionTrait>() } ?: ""
val equatableConformance = (": " + SwiftTypes.Protocols.Equatable + " ").takeIf { shape.hasTrait<EquatableConformanceTrait>() } ?: ""
writer.openBlock("public ${indirectOrNot}enum \$union.name:L $equatableConformance{", "}\n") {
val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait<EquatableConformanceTrait>() } ?: ""
writer.openBlock("public ${indirectOrNot}enum \$union.name:L: \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable) {
// event streams (@streaming union) MAY have variants that target errors.
// These errors if encountered on the stream will be thrown as an exception rather
// than showing up as one of the possible events the consumer will see on the stream (AsyncThrowingStream<T>).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ object SwiftTypes {
val RawRepresentable = builtInSymbol("RawRepresentable", SwiftDeclaration.PROTOCOL)
val CaseIterable = builtInSymbol("CaseIterable", SwiftDeclaration.PROTOCOL)
val CustomDebugStringConvertible = builtInSymbol("CustomDebugStringConvertible", SwiftDeclaration.PROTOCOL)
val Sendable = builtInSymbol("Sendable", SwiftDeclaration.PROTOCOL)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class PaginatorGeneratorTest {
// Equatable conformance must have been generated for struct nested inside a pagination token.
val contents = getFileContents(context.manifest, "Sources/Test/models/NestedInputTokenValue.swift")
val expected = """
public struct NestedInputTokenValue : Swift.Equatable {
public struct NestedInputTokenValue: Swift.Sendable, Swift.Equatable {
""".trimIndent()
contents.shouldContainOnlyOnce(expected)
}
Expand All @@ -161,7 +161,7 @@ class PaginatorGeneratorTest {
// Equatable conformance must have been generated for struct nested under pagination token.
val contents = getFileContents(context.manifest, "Sources/Test/models/DoublyNestedInputTokenValue.swift")
val expected = """
public struct DoublyNestedInputTokenValue : Swift.Equatable {
public struct DoublyNestedInputTokenValue: Swift.Sendable, Swift.Equatable {
""".trimIndent()
contents.shouldContainOnlyOnce(expected)
}
Expand All @@ -172,8 +172,8 @@ class PaginatorGeneratorTest {
// Equatable conformance must have been generated for union nested under pagination token.
val contents = getFileContents(context.manifest, "Sources/Test/models/InputPaginationUnion.swift")
val expected = """
public enum InputPaginationUnion : Swift.Equatable {
""".trimIndent()
public enum InputPaginationUnion: Swift.Sendable, Swift.Equatable {
"""
contents.shouldContainOnlyOnce(expected)
}

Expand Down
Loading

0 comments on commit ea842e8

Please sign in to comment.