Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 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
}
}
4 changes: 4 additions & 0 deletions Sources/ClientRuntime/Config/ClientConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
// SPDX-License-Identifier: Apache-2.0
//

/// Base protocol for client configuration.
///
/// Note: This protocol will inherit `Sendable` after the deprecation period of class-based configurations.
/// The struct-based configuration already conforms to `Sendable` via `@unchecked Sendable`.
public protocol ClientConfiguration {
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
@@ -0,0 +1,29 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse

/// A `Sendable` wrapper for `HttpInterceptorProvider` that enables safe concurrent access.
///
/// This wrapper allows non-`Sendable` HTTP interceptor providers to be stored in `Sendable` contexts
/// by boxing them and forwarding method calls.
///
/// Note: Uses `@unchecked Sendable` because the wrapper is designed to safely encapsulate
/// non-Sendable providers for use in concurrent contexts. The safety is ensured by the
/// immutability of the stored provider reference.
public struct SendableHttpInterceptorProviderBox: HttpInterceptorProvider, @unchecked Sendable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as below, see comment on SendableInterceptorProviderBox

internal let _provider: any HttpInterceptorProvider

public init(_ provider: any HttpInterceptorProvider) {
self._provider = provider
}

public func create<InputType, OutputType>() -> any Interceptor<InputType, OutputType, HTTPRequest, HTTPResponse> {
return _provider.create()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import protocol Smithy.RequestMessage
import protocol Smithy.ResponseMessage

/// A `Sendable` wrapper for `InterceptorProvider` that enables safe concurrent access.
///
/// This wrapper allows non-`Sendable` interceptor providers to be stored in `Sendable` contexts
/// by boxing them and forwarding method calls.
///
/// Note: Uses `@unchecked Sendable` because the wrapper is designed to safely encapsulate
/// non-Sendable providers for use in concurrent contexts. The safety is ensured by the
/// immutability of the stored provider reference.
public struct SendableInterceptorProviderBox: InterceptorProvider, @unchecked Sendable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will be able to remove these wrapper types once we deprecate the class based config. The purpose of these wrappers is to allow the new struct based config to fully support being marked as sendable.

internal let _provider: any InterceptorProvider

public init(_ provider: any InterceptorProvider) {
self._provider = provider
}

public func create<
InputType,
OutputType,
RequestType: RequestMessage,
ResponseType: ResponseMessage
>() -> any Interceptor<InputType, OutputType, RequestType, ResponseType> {
return _provider.create()
}
}
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
}
Loading
Loading