Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Concurrency Warnings in Swift 6 #310

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import AwsCAuth
import Foundation

public final class Credentials {
// We can't mutate this class after initialization. Swift can not verify the sendability due to OpaquePointer,
// So mark it unchecked Sendable
public final class Credentials: @unchecked Sendable {

let rawValue: OpaquePointer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public protocol CredentialsProviding {
func getCredentials() async throws -> Credentials
}

public class CredentialsProvider: CredentialsProviding {
// We can't mutate this class after initialization. Swift can not verify the sendability due to pointer,
// So mark it unchecked Sendable
public class CredentialsProvider: CredentialsProviding, @unchecked Sendable {

let rawValue: UnsafeMutablePointer<aws_credentials_provider>

Expand Down Expand Up @@ -590,7 +592,7 @@ private func onGetCredentials(credentials: OpaquePointer?,
// We need to share this pointer to C in a task block but Swift compiler complains
// that Pointer does not conform to Sendable. Wrap the pointer in a @unchecked Sendable block
// for Swift compiler to stop complaining.
struct SendablePointer: @unchecked Sendable {
struct SendableRawPointer: @unchecked Sendable {
let pointer: UnsafeMutableRawPointer
}

Expand All @@ -600,12 +602,13 @@ private func getCredentialsDelegateFn(_ delegatePtr: UnsafeMutableRawPointer!,
Int32,
UnsafeMutableRawPointer?) -> Void)!,
_ userData: UnsafeMutableRawPointer!) -> Int32 {
let delegate = Unmanaged<Box<CredentialsProviding>>
.fromOpaque(delegatePtr)
.takeUnretainedValue().contents
let userData = SendablePointer(pointer: userData)
let userData = SendableRawPointer(pointer: userData)
let delegatePtr = SendableRawPointer(pointer: delegatePtr)
Task {
do {
let delegate = Unmanaged<Box<CredentialsProviding>>
.fromOpaque(delegatePtr.pointer)
.takeUnretainedValue().contents
let credentials = try await delegate.getCredentials()
callbackFn(credentials.rawValue, AWS_OP_SUCCESS, userData.pointer)
} catch CommonRunTimeError.crtError(let crtError) {
Expand Down
2 changes: 1 addition & 1 deletion Source/AwsCommonRuntimeKit/auth/imds/IAMProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import AwsCAuth
import Foundation

public struct IAMProfile {
public struct IAMProfile: Sendable {
public let lastUpdated: Date
public let profileArn: String
public let profileId: String
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/auth/imds/IMDSClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import AwsCAuth

// swiftlint:disable type_body_length
public class IMDSClient {
// We can't mutate this class after initialization. Swift can not verify the sendability due to OpaquePointer,
// So mark it unchecked Sendable
public class IMDSClient: @unchecked Sendable {
let rawValue: OpaquePointer

/// Creates an IMDSClient that always uses IMDSv2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import AwsCAuth
import Foundation

public struct IMDSInstanceInfo {
public struct IMDSInstanceInfo: Sendable {
public let marketPlaceProductCodes: [String]
public let availabilityZone: String
public let privateIp: String
Expand Down
10 changes: 7 additions & 3 deletions Source/AwsCommonRuntimeKit/auth/signing/Signer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ public class Signer {
}
}

class SignRequestCore {
// After signing, we mutate the request and resume the continuation, which may result in a thread change.
// We won't modify it after continuation.resume is called. So we can mark it @unchecked Sendable
class SignRequestCore: @unchecked Sendable {
let request: HTTPRequestBase
var continuation: CheckedContinuation<HTTPRequestBase, Error>
let continuation: CheckedContinuation<HTTPRequestBase, Error>
let shouldSignHeader: ((String) -> Bool)?
init(request: HTTPRequestBase,
continuation: CheckedContinuation<HTTPRequestBase, Error>,
Expand Down Expand Up @@ -207,6 +209,7 @@ private func onRequestSigningComplete(signingResult: UnsafeMutablePointer<aws_si
} else {
signRequestCore.continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
}
// It's not thread-safe to modify `signingRequestCore.request` after continuation.resume
}

private func onSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_result>?,
Expand All @@ -220,9 +223,10 @@ private func onSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_r

// Success
var awsStringPointer: UnsafeMutablePointer<aws_string>!
let signature = AWSString("signature")
guard aws_signing_result_get_property(
signingResult!,
g_aws_signature_property_name,
signature.rawValue,
&awsStringPointer) == AWS_OP_SUCCESS else {
chunkSignerCore.continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
return
Expand Down
12 changes: 9 additions & 3 deletions Source/AwsCommonRuntimeKit/crt/Allocator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
// SPDX-License-Identifier: Apache-2.0.
import AwsCCommon

/**
The default allocator.
You are probably looking to use `allocator` instead.
/*
* The default allocator.
* We need to declare `allocator` as mutable (`var`) instead of `let` because we override it with a tracing allocator in tests. This is not mutated anywhere else apart from the start of tests.
* Swift compiler doesn't let us compile this code in Swift 6 due to global shared mutable state without locks, and complains that this is not safe. Disable the safety here since we won't modify it.
* Remove the Ifdef once our minimum supported Swift version reaches 5.10
*/
#if swift(>=5.10)
nonisolated(unsafe) var allocator = aws_default_allocator()!
#else
var allocator = aws_default_allocator()!
#endif

/// An allocator is used to allocate memory on the heap.
protocol Allocator {
Expand Down
2 changes: 1 addition & 1 deletion Source/AwsCommonRuntimeKit/crt/CommonRuntimeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum CommonRunTimeError: Error {
case crtError(CRTError)
}

public struct CRTError: Equatable {
public struct CRTError: Equatable, Sendable {
public let code: Int32
public let message: String
public let name: String
Expand Down
4 changes: 2 additions & 2 deletions Source/AwsCommonRuntimeKit/crt/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public enum LogTarget {
case filePath(String)
}

public struct Logger {
public actor Logger {
private static var logger: aws_logger?
private static let lock = NSLock()

Expand Down Expand Up @@ -55,7 +55,7 @@ public struct Logger {
}
}

public enum LogLevel {
public enum LogLevel: Sendable {
case none
case fatal
case error
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTP1Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import AwsCHttp
import Foundation

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// An HTTP1Stream represents a single HTTP/1.1 specific Http Request/Response.
public class HTTP1Stream: HTTPStream {
public class HTTP1Stream: HTTPStream, @unchecked Sendable {
/// Stream keeps a reference to HttpConnection to keep it alive
private let httpConnection: HTTPClientConnection

Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTP2ClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import AwsCHttp
import AwsCIo
import Foundation

public class HTTP2ClientConnection: HTTPClientConnection {
// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
public class HTTP2ClientConnection: HTTPClientConnection, @unchecked Sendable {

/// Creates a new http2 stream from the `HTTPRequestOptions` given.
/// - Parameter requestOptions: An `HTTPRequestOptions` struct containing callbacks on
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTP2Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import AwsCHttp
import Foundation

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// An HTTP2Stream represents a single HTTP/2 specific HTTP Request/Response.
public class HTTP2Stream: HTTPStream {
public class HTTP2Stream: HTTPStream, @unchecked Sendable {
private let httpConnection: HTTPClientConnection?

// Called by Connection Manager
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTP2StreamManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// SPDX-License-Identifier: Apache-2.0.
import AwsCHttp

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// Manages a Pool of HTTP/2 Streams. Creates and manages HTTP/2 connections under the hood.
public class HTTP2StreamManager {
public class HTTP2StreamManager: @unchecked Sendable {
let rawValue: UnsafeMutablePointer<aws_http2_stream_manager>

public init(options: HTTP2StreamManagerOptions) throws {
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTPClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import AwsCIo
import Foundation

// swiftlint:disable force_try
public class HTTPClientConnection {
// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
public class HTTPClientConnection: @unchecked Sendable {
let rawValue: UnsafeMutablePointer<aws_http_connection>
/// This will keep the connection manager alive until connection is alive
let manager: HTTPClientConnectionManager
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/http/HTTPStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import AwsCHttp
import Foundation

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// An base abstract class that represents a single Http Request/Response for both HTTP/1.1 and HTTP/2.
/// Can be used to update the Window size, and get status code.
public class HTTPStream {
public class HTTPStream: @unchecked Sendable {
let rawValue: UnsafeMutablePointer<aws_http_stream>
var callbackData: HTTPStreamCallbackCore

Expand Down
2 changes: 1 addition & 1 deletion Source/AwsCommonRuntimeKit/io/HostAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import AwsCIo

/// Represents a single HostAddress resolved by the Host Resolver
public struct HostAddress: CStruct {
public struct HostAddress: CStruct, Sendable {

/// Address type is ipv4 or ipv6
public let addressType: HostAddressType
Expand Down
2 changes: 1 addition & 1 deletion Source/AwsCommonRuntimeKit/io/HostAddressType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import AwsCIo

/// Type of Host Address (ipv4 or ipv6)
public enum HostAddressType {
public enum HostAddressType: Sendable {
case A
case AAAA
}
Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/io/HostResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ public protocol HostResolverProtocol {
func purgeCache() async
}

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// CRT Host Resolver which performs async DNS lookups
public class HostResolver: HostResolverProtocol {
public class HostResolver: HostResolverProtocol, @unchecked Sendable {
let rawValue: UnsafeMutablePointer<aws_host_resolver>
let maxTTL: Int

Expand Down
4 changes: 3 additions & 1 deletion Source/AwsCommonRuntimeKit/io/retryer/RetryToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import AwsCIo

// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
/// This is just a wrapper for aws_retry_token which user can not create themself but pass around once acquired.
public class RetryToken {
public class RetryToken: @unchecked Sendable {
let rawValue: UnsafeMutablePointer<aws_retry_token>

init(rawValue: UnsafeMutablePointer<aws_retry_token>) {
Expand Down
9 changes: 5 additions & 4 deletions Source/Elasticurl/CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import Foundation
import AwsCCommon
// swiftlint:disable trailing_whitespace

struct CommandLineParser {
/// A function to parse command line arguments
Expand All @@ -27,8 +26,8 @@ struct CommandLineParser {
break
}
if let char = UnicodeScalar(Int(opt)) {
if aws_cli_optarg != nil {
argumentsDict[String(char)] = String(cString: aws_cli_optarg)
if aws_get_cli_optarg() != nil {
argumentsDict[String(char)] = String(cString: aws_get_cli_optarg())
} else {
// if argument doesnt have a value just mark it as present in the dictionary
argumentsDict[String(char)] = true
Expand Down Expand Up @@ -60,7 +59,9 @@ extension CLIHasArg: RawRepresentable, CaseIterable {
}
}

class AWSCLIOption {
// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer.
// So mark it as unchecked Sendable.
class AWSCLIOption: @unchecked Sendable {
let rawValue: aws_cli_option
let name: UnsafeMutablePointer<CChar>
init(name: String, hasArg: CLIHasArg, flag: UnsafeMutablePointer<Int32>? = nil, val: String) {
Expand Down
23 changes: 13 additions & 10 deletions Source/Elasticurl/Elasticurl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import AwsCommonRuntimeKit
import Foundation

// swiftlint:disable cyclomatic_complexity function_body_length
struct Context {
struct Context: @unchecked Sendable {
// args
public var logLevel: LogLevel = .trace
public var verb: String = "GET"
Expand All @@ -29,9 +29,8 @@ struct Context {
@main
struct Elasticurl {
private static let version = "0.1.0"
private static var context = Context()

static func parseArguments() {
static func parseArguments() -> Context {
let optionString = "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWh"
let options = [ElasticurlOptions.caCert.rawValue,
ElasticurlOptions.caPath.rawValue,
Expand Down Expand Up @@ -63,6 +62,7 @@ struct Elasticurl {
arguments: CommandLine.unsafeArgv,
optionString: optionString, options: options)

var context = Context()
if let caCert = argumentsDict["a"] as? String {
context.caCert = caCert
}
Expand Down Expand Up @@ -176,6 +176,7 @@ struct Elasticurl {
exit(-1)
}
context.url = url
return context
}

static func showHelp() {
Expand All @@ -202,22 +203,24 @@ struct Elasticurl {
print(" -h, --help: Display this message and quit.")
}

static func createOutputFile() {
static func createOutputFile(context: Context) -> Context {
var context = context
if let fileName = context.outputFileName {
let fileManager = FileManager.default
let path = FileManager.default.currentDirectoryPath + "/" + fileName
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
context.outputStream = FileHandle(forWritingAtPath: fileName) ?? FileHandle.standardOutput
}
return context
}

static func writeData(data: Data) {
static func writeData(data: Data, context: Context) {
context.outputStream.write(data)
}

static func main() async {
parseArguments()
createOutputFile()
var context = parseArguments()
context = createOutputFile(context: context)
if let traceFile = context.traceFile {
print("enable logging with trace file")
try? Logger.initialize(target: .filePath(traceFile), level: context.logLevel)
Expand All @@ -226,10 +229,10 @@ struct Elasticurl {
try? Logger.initialize(target: .standardOutput, level: context.logLevel)
}

await run()
await run(context)
}

static func run() async {
static func run(_ context: Context) async {
do {
guard let host = context.url.host else {
print("no proper host was parsed from the url. quitting.")
Expand Down Expand Up @@ -291,7 +294,7 @@ struct Elasticurl {
}

let onBody: HTTPRequestOptions.OnIncomingBody = { bodyChunk in
writeData(data: bodyChunk)
writeData(data: bodyChunk, context: context)
}

let onComplete: HTTPRequestOptions.OnStreamComplete = { result in
Expand Down
Loading
Loading