Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ed3ec20
make a bunch of stuff Sendable
dayaffe Dec 11, 2025
27db4a0
add Sendable to SDK codegen
dayaffe Dec 11, 2025
8899fcb
Merge branch 'main' into day/make-everything-sendable
dayaffe Dec 11, 2025
0e7f5d2
fix sendable on endpoints
dayaffe Dec 12, 2025
d2ec83b
fix swift6 compatibility
dayaffe Dec 12, 2025
dd02d3b
fixes + make URLSessionHTTPClient sendable
dayaffe Dec 12, 2025
a8348ad
use final class for swift6 compatibility
dayaffe Dec 12, 2025
9f993b5
fix test
dayaffe Dec 12, 2025
8cfaac4
Sendable overhaul
dayaffe Dec 29, 2025
3b61b79
Merge branch 'main' into day/make-everything-sendable
dayaffe Dec 29, 2025
2cd0055
ktlint and fix test
dayaffe Dec 29, 2025
6cc642f
Remove explicit imports
dayaffe Dec 29, 2025
f4027b4
Use var instead of let
dayaffe Dec 29, 2025
f711449
ktlint fix
dayaffe Dec 29, 2025
6d202c6
fix failing integration tests
dayaffe Dec 29, 2025
e70bf74
address PR comments
dayaffe Jan 9, 2026
3cb3cb2
more issues fixed
dayaffe Jan 12, 2026
08ce751
remove force cast for swiftlint
dayaffe Jan 12, 2026
e00911e
a test fix and ktlint and hoping I fixed swiftlint line length violat…
dayaffe Jan 12, 2026
2bba117
fix plugins
dayaffe Jan 12, 2026
dcce994
add back missing types
dayaffe Jan 15, 2026
04e34b0
Merge branch 'main' into day/make-everything-sendable
dayaffe Jan 15, 2026
2561ddd
make sendable change backwards compatible
dayaffe Jan 26, 2026
cbb5b0d
ktlint
dayaffe Jan 26, 2026
22fce59
remove throwing from class init
dayaffe Jan 27, 2026
c0621bd
try to fix issues
dayaffe Jan 27, 2026
de11664
use a wrapper for sendable conformance of interceptor providers
dayaffe Jan 29, 2026
ee88a8c
add missing box types
dayaffe Jan 29, 2026
38842f1
lint
dayaffe Jan 29, 2026
7b6f40e
wrapper types need to be unchecked for swift6
dayaffe Jan 29, 2026
9b5b796
Merge branch 'main' into day/make-everything-sendable
dayaffe Feb 2, 2026
c6f86b2
Merge branch 'main' into day/make-everything-sendable
dayaffe Feb 3, 2026
ab27b30
ktlint
dayaffe Feb 3, 2026
a2841f9
Merge branch 'main' into day/make-everything-sendable
jbelkins Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// SPDX-License-Identifier: Apache-2.0
//
public protocol Client {
public protocol Client: Sendable {
associatedtype Config: ClientConfiguration
init(config: Config)
}
16 changes: 13 additions & 3 deletions Sources/ClientRuntime/Client/ClientBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,21 @@ public class ClientBuilder<ClientType: Client> {
return ClientType(config: configuration)
}

enum ClientBuilderError: Error {
case incompatibleConfigurationType(expected: String, received: String)
}

func resolve(plugins: [any Plugin]) async throws -> ClientType.Config {
let clientConfiguration = try await ClientType.Config()
var clientConfiguration: any ClientConfiguration = try await ClientType.Config()
for plugin in plugins {
try await plugin.configureClient(clientConfiguration: clientConfiguration)
try await plugin.configureClient(clientConfiguration: &clientConfiguration)
}
guard let typedConfig = clientConfiguration as? ClientType.Config else {
throw ClientBuilderError.incompatibleConfigurationType(
expected: String(describing: ClientType.Config.self),
received: String(describing: type(of: clientConfiguration))
)
}
return clientConfiguration
return typedConfig
}
}
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Config/ClientConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
// SPDX-License-Identifier: Apache-2.0
//

public protocol ClientConfiguration {
public protocol ClientConfiguration: Sendable {
init() async throws
}
13 changes: 7 additions & 6 deletions Sources/ClientRuntime/Config/Context+Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ private let clientConfigKey = AttributeKey<ClientConfigurationWrapper>(name: "Sm

/// A wrapper used to allow a client configuration object to be placed in Context, since client config is not Sendable.
///
/// Placing the client config into Context is safe because the client config is not modified after being placed into Context.
/// Client config is unwrapped, then may be used to create a service client and make calls as part of performing an operation.
/// Placing the client config into Context is safe because the client config is not modified after
/// being placed into Context. Client config is unwrapped, then may be used to create a service
/// client and make calls as part of performing an operation.
///
/// This type is public so that it may be accessed in other runtime modules. It is protected as SPI because it is a cross-module
/// implementation detail that does not affect customers.
/// This type is public so that it may be accessed in other runtime modules. It is protected as
/// SPI because it is a cross-module implementation detail that does not affect customers.
///
/// `@unchecked Sendable` is used to make the wrapper Sendable even though it is technically not, due to the non-Sendable
/// client config stored within.
/// `@unchecked Sendable` is used to make the wrapper Sendable even though it is technically not,
/// due to the non-Sendable client config stored within.
@_spi(ClientConfigWrapper)
public final class ClientConfigurationWrapper: @unchecked Sendable {
public let clientConfig: DefaultClientConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public protocol DefaultClientConfiguration: ClientConfiguration {
/// Adds an `InterceptorProvider` that will be used to provide interceptors for all operations.
///
/// - Parameter provider: The `InterceptorProvider` to add.
func addInterceptorProvider(_ provider: InterceptorProvider)
mutating func addInterceptorProvider(_ provider: InterceptorProvider)

/// TODO(plugins): Add Checksum, etc.
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration {
/// An ordered, prioritized list of auth scheme IDs that should be used for this client's requests.
///
/// If no auth scheme preference is given, the first supported auth scheme defined in `authSchemes`
/// will be used. If a value was not provided for `authSchemes`, then the service's first defined, supported auth scheme will be used.
/// will be used. If a value was not provided for `authSchemes`, then the service's first defined,
/// supported auth scheme will be used.
var authSchemePreference: [String]? { get set }

/// The auth scheme resolver to use for resolving the auth scheme.
Expand All @@ -51,5 +52,5 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration {
/// Adds a `HttpInterceptorProvider` that will be used to provide interceptors for all HTTP operations.
///
/// - Parameter provider: The `HttpInterceptorProvider` to add.
func addInterceptorProvider(_ provider: HttpInterceptorProvider)
mutating func addInterceptorProvider(_ provider: HttpInterceptorProvider)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public struct DefaultSDKRuntimeConfiguration<DefaultSDKRuntimeRetryStrategy: Ret

/// The HTTP client to use for HTTP connections.
///
/// By default, Swift SDK will set this to `CRTClientEngine` client on Linux platforms, and `URLSessionHttpClient` on Apple platforms.
/// By default, Swift SDK will set this to `CRTClientEngine` client on Linux platforms, and
/// `URLSessionHttpClient` on Apple platforms.
public var httpClientEngine: HTTPClient

/// The HTTP client configuration.
Expand Down Expand Up @@ -114,7 +115,8 @@ public extension DefaultSDKRuntimeConfiguration {
/// The default idempotency token generator that returns UUIDs.
static var defaultIdempotencyTokenGenerator: IdempotencyTokenGenerator { DefaultIdempotencyTokenGenerator() }

/// The default retry strategy options with the exponential backoff strategy & other defaults defined in `RetryStrategyOptions`.
/// The default retry strategy options with the exponential backoff strategy & other defaults
/// defined in `RetryStrategyOptions`.
static var defaultRetryStrategyOptions: RetryStrategyOptions {
RetryStrategyOptions(backoffStrategy: ExponentialBackoffStrategy())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import struct SmithyHTTPAPI.Endpoint
import enum SmithyHTTPAPI.EndpointPropertyValue
import struct SmithyHTTPAPI.Headers

public struct DefaultEndpointResolver<Params: EndpointsRequestContextProviding> {
public struct DefaultEndpointResolver<Params: EndpointsRequestContextProviding>: Sendable {

private let engine: ClientRuntime.EndpointsRuleEngine

Expand Down
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Endpoints/EndpointsRuleEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import AwsCommonRuntimeKit

/// Wrapper for CRTAWSEndpointsRuleEngine
public class EndpointsRuleEngine {
public final class EndpointsRuleEngine: @unchecked Sendable {

let crtEngine: AwsCommonRuntimeKit.EndpointsRuleEngine

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import struct SmithyHTTPAPI.Endpoint

public struct StaticEndpointResolver<Params: EndpointsRequestContextProviding> {
public struct StaticEndpointResolver<Params: EndpointsRequestContextProviding>: Sendable {

private let endpoint: SmithyHTTPAPI.Endpoint

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import class SmithyHTTPAPI.HTTPResponse
/// Provides implementations of `HttpInterceptor`.
///
/// For the generic counterpart, see `InterceptorProvider`.
public protocol HttpInterceptorProvider {
public protocol HttpInterceptorProvider: Sendable {

/// Creates an instance of an `HttpInterceptor` implementation.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import protocol Smithy.ResponseMessage
///
/// This can be used to create `Interceptor`s that are generic on their Request/Response/Attributes
/// types, when you don't have access to the exact types until later.
public protocol InterceptorProvider {
public protocol InterceptorProvider: Sendable {

/// Creates an instance of an `Interceptor` implementation, specialized on the given
/// `RequestType` and `ResponseType`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import Glibc
import Darwin
#endif

public class CRTClientEngine: HTTPClient {
public class CRTClientEngine: HTTPClient, @unchecked Sendable {
public static let noOpCrtClientEngineTelemetry = HttpTelemetry(
httpScope: "CRTClientEngine",
telemetryProvider: DefaultTelemetry.provider
Expand Down Expand Up @@ -171,7 +171,7 @@ public class CRTClientEngine: HTTPClient {

public typealias StreamContinuation = CheckedContinuation<HTTPResponse, Error>
private let telemetry: HttpTelemetry
private var logger: LogAgent
private let logger: LogAgent
private let serialExecutor: SerialExecutor
private let CONTENT_LENGTH_HEADER = "Content-Length"
private let AWS_COMMON_RUNTIME = "AwsCommonRuntime"
Expand Down
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Networking/Http/SdkHttpClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse

/// this class will implement Handler per new middleware implementation
public class SdkHttpClient: ExecuteRequest {
public class SdkHttpClient: ExecuteRequest, @unchecked Sendable {

let engine: HTTPClient

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ import class SmithyStreams.BufferedStream

/// A client that can be used to make requests to AWS services using `Foundation`'s `URLSession` HTTP client.
///
/// This client is usable on all Swift platforms that support both the `URLSession` library and Objective-C interoperability features
/// (these are generally the Apple platforms.)
/// This client is usable on all Swift platforms that support both the `URLSession` library and
/// Objective-C interoperability features (these are generally the Apple platforms.)
///
/// Use of this client is recommended on all Apple platforms, and is required on Apple Watch ( see
/// [TN3135: Low-level networking on watchOS](https://developer.apple.com/documentation/technotes/tn3135-low-level-networking-on-watchos)
/// for details about allowable modes of networking on the Apple Watch platform.)
///
/// On Linux platforms, we recommend using the CRT-based HTTP client for its configurability and performance.
public final class URLSessionHTTPClient: HTTPClient {
public final class URLSessionHTTPClient: HTTPClient, @unchecked Sendable {
public static let noOpURLSessionHTTPClientTelemetry = HttpTelemetry(
httpScope: "URLSessionHTTPClient",
telemetryProvider: DefaultTelemetry.provider
Expand Down Expand Up @@ -107,7 +107,8 @@ public final class URLSessionHTTPClient: HTTPClient {
/// `urlSession(_:task:didCompleteWithError)` for this connection.
var error: Error?

/// A response stream that streams the response back to the caller. Data is buffered in-memory until read by the caller.
/// A response stream that streams the response back to the caller. Data is buffered
/// in-memory until read by the caller.
let responseStream = BufferedStream()

/// Creates a new connection object
Expand Down Expand Up @@ -406,7 +407,7 @@ public final class URLSessionHTTPClient: HTTPClient {
private let telemetry: HttpTelemetry

/// The logger for this HTTP client.
private var logger: LogAgent
private let logger: LogAgent

/// The TLS options for this HTTP client.
private let tlsConfiguration: URLSessionTLSOptions?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import enum SmithyHTTPAPI.SmithyHTTPAPIKeys
/// Container for Orchestrator telemetry, including configurable attributes and names.
///
/// Note: This is intended to be used within generated code, not directly.
public class OrchestratorTelemetry {
public final class OrchestratorTelemetry: Sendable {
internal let contextManager: any TelemetryContextManager
internal let tracerProvider: any TracerProvider

Expand Down
19 changes: 13 additions & 6 deletions Sources/ClientRuntime/Pagination/PaginatorSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,22 @@ public struct PaginatorSequence<OperationStackInput: PaginateToken, OperationSta
var token: OperationStackInput.Token?
var isFirstPage: Bool = true

// swiftlint:disable force_cast
public mutating func next() async throws -> OperationStackOutput? {
while token != nil || isFirstPage {

if let token = token,
(token is String && !(token as! String).isEmpty) ||
(token is [String: Any] && !(token as! [String: Any]).isEmpty) {
self.input = input.usingPaginationToken(token)
if let token = token {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed for readability when solving a bug, allows us to remove swiftlint disable

let shouldUsePaginationToken: Bool
if let stringToken = token as? String {
shouldUsePaginationToken = !stringToken.isEmpty
} else if let dictToken = token as? [String: Any] {
shouldUsePaginationToken = !dictToken.isEmpty
} else {
shouldUsePaginationToken = true
}

if shouldUsePaginationToken {
self.input = input.usingPaginationToken(token)
}
}
let output = try await sequence.paginationFunction(input)
isFirstPage = false
Expand All @@ -62,7 +70,6 @@ public struct PaginatorSequence<OperationStackInput: PaginateToken, OperationSta
}
return nil
}
// swiftlint:enable force_cast
}

public func makeAsyncIterator() -> PaginationIterator {
Expand Down
16 changes: 8 additions & 8 deletions Sources/ClientRuntime/Plugins/AuthSchemePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ public class AuthSchemePlugin: Plugin {
self.authSchemes = authSchemes
}

public func configureClient(clientConfiguration: ClientConfiguration) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per discussion, param should be clientConfiguration: inout ClientConfiguration

if var config = clientConfiguration as? DefaultHttpClientConfiguration {
if self.authSchemes != nil {
config.authSchemes = self.authSchemes!
}
if self.authSchemeResolver != nil {
config.authSchemeResolver = self.authSchemeResolver!
}
public func configureClient(clientConfiguration: inout ClientConfiguration) async throws {
guard var config = clientConfiguration as? any DefaultHttpClientConfiguration else { return }
if self.authSchemes != nil {
config.authSchemes = self.authSchemes!
}
if self.authSchemeResolver != nil {
config.authSchemeResolver = self.authSchemeResolver!
}
clientConfiguration = config
}
}
8 changes: 5 additions & 3 deletions Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import struct SmithyRetries.DefaultRetryStrategy

public class DefaultClientPlugin: Plugin {
public init() {}
public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultClientConfiguration {
public func configureClient(clientConfiguration: inout ClientConfiguration) async throws {
if var config = clientConfiguration as? any DefaultClientConfiguration {
config.retryStrategyOptions =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.defaultRetryStrategyOptions
clientConfiguration = config
}

if var config = clientConfiguration as? DefaultHttpClientConfiguration {
if var config = clientConfiguration as? any DefaultHttpClientConfiguration {
let httpClientConfiguration =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.defaultHttpClientConfiguration
config.httpClientConfiguration = httpClientConfiguration
config.httpClientEngine =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.makeClient(httpClientConfiguration: httpClientConfiguration)
clientConfiguration = config
}
}
}
10 changes: 5 additions & 5 deletions Sources/ClientRuntime/Plugins/HttpClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public class DefaultHttpClientPlugin: Plugin {
)
}

public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultHttpClientConfiguration {
config.httpClientConfiguration = self.httpClientConfiguration
config.httpClientEngine = self.httpClient
}
public func configureClient(clientConfiguration: inout ClientConfiguration) async throws {
guard var config = clientConfiguration as? any DefaultHttpClientConfiguration else { return }
config.httpClientConfiguration = self.httpClientConfiguration
config.httpClientEngine = self.httpClient
clientConfiguration = config
}
}
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Plugins/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
//

public protocol Plugin {
func configureClient(clientConfiguration: ClientConfiguration) async throws
func configureClient(clientConfiguration: inout ClientConfiguration) async throws
}
8 changes: 4 additions & 4 deletions Sources/ClientRuntime/Plugins/RetryPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public class RetryPlugin: Plugin {
self.retryStrategyOptions = retryStrategyOptions
}

public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultClientConfiguration {
config.retryStrategyOptions = self.retryStrategyOptions
}
public func configureClient(clientConfiguration: inout ClientConfiguration) async throws {
guard var config = clientConfiguration as? any DefaultClientConfiguration else { return }
config.retryStrategyOptions = self.retryStrategyOptions
clientConfiguration = config
}
}
8 changes: 4 additions & 4 deletions Sources/ClientRuntime/Plugins/TelemetryPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public class TelemetryPlugin: Plugin {
)
}

public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultClientConfiguration {
config.telemetryProvider = self.telemetryProvider
}
public func configureClient(clientConfiguration: inout ClientConfiguration) async throws {
guard var config = clientConfiguration as? any DefaultClientConfiguration else { return }
config.telemetryProvider = self.telemetryProvider
clientConfiguration = config
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

/// Additional logging opt-in for request / response flow. For each selected option other than `.none`, the additional info gets logged at `.debug` level by the `LoggingMiddleware`.
public enum ClientLogMode {
public enum ClientLogMode: Sendable {
case none
case request
case requestWithBody
Expand Down
8 changes: 5 additions & 3 deletions Sources/ClientRuntime/Util/SwiftVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
/// Returns the swift version of the compiler that is compiling this application.
public var swiftVersion: String {
/**
Unfortunately there isn't a way to grab the compiled swift programmatically and so we must resort to the compiler directives to produce a version string.
We are checking for quite a few versions in the future, that may never exist, in order to future proof our current SDKs. Ideally, all current SDKs should compile
on future Swift versions unless that Swift version introduces a breaking change.
Unfortunately there isn't a way to grab the compiled swift programmatically and so we must
resort to the compiler directives to produce a version string.
We are checking for quite a few versions in the future, that may never exist, in order to
future proof our current SDKs. Ideally, all current SDKs should compile on future Swift
versions unless that Swift version introduces a breaking change.

TODO add handling for Swift 8.x versions when Swift 6.0 is released.
*/
Expand Down
2 changes: 1 addition & 1 deletion Sources/SmithyHTTPAPI/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

/// The interface for a client that can be used to perform SDK operations over HTTP.
public protocol HTTPClient {
public protocol HTTPClient: Sendable {

/// Executes an HTTP request to perform an SDK operation.
///
Expand Down
Loading
Loading