From d0f41df58775f931299a42b81e32b9c58e5304a4 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Tue, 19 Mar 2024 20:00:47 -0400 Subject: [PATCH] Swift 6: Fix concurrency warnings (#23) * Fix concurrency warnings * Update json api * Update readme * Build on swift 5.9 --- .github/workflows/build.yml | 6 +-- .github/workflows/codeql.yml | 38 ------------------ Package.resolved | 4 +- Package.swift | 30 +++++++++++--- README.md | 2 +- Sources/Compute/Console.swift | 9 ++--- Sources/Compute/Fanout/FanoutMessage.swift | 18 +++------ .../Compute/Fastly/FastlyEnvironment.swift | 16 ++++---- Sources/Compute/Fastly/FastlyStubs.swift | 2 +- Sources/Compute/Fetch/Fetch+Wasi.swift | 39 +++++++++++-------- Sources/Compute/Fetch/FetchRequest.swift | 4 +- Sources/Compute/Fetch/FetchResponse.swift | 6 +-- Sources/Compute/JWT/JWT.swift | 10 ++--- Sources/Compute/KVStore.swift | 4 +- .../ReadableBody/ReadableBody+Data.swift | 8 ++-- .../ReadableBody/ReadableBody+Wasi.swift | 9 ++--- .../Compute/ReadableBody/ReadableBody.swift | 2 +- .../ComputeRuntime/include/ComputeRuntime.h | 2 +- 18 files changed, 94 insertions(+), 115 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14988d29..ef49792f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,11 +5,11 @@ on: push jobs: build: runs-on: ubuntu-latest - container: ghcr.io/swiftwasm/swift:5.7 + container: ghcr.io/swiftwasm/swift:5.9 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: swift --version - - run: swift build -c debug --triple wasm32-unknown-wasi \ No newline at end of file + - run: swift build -c debug --triple wasm32-unknown-wasi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 25fc3a87..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: true - -jobs: - analyze: - name: Analyze - runs-on: macos-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: swift - - - run: swift build --target Compute - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:swift" \ No newline at end of file diff --git a/Package.resolved b/Package.resolved index f7bc5c4b..5c69193f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto", "state" : { - "revision" : "cc76b894169a3c86b71bac10c78a4db6beb7a9ad", - "version" : "3.2.0" + "revision" : "f0525da24dc3c6cbb2b6b338b65042bc91cbc4bb", + "version" : "3.3.0" } } ], diff --git a/Package.swift b/Package.swift index 368aa837..144a8058 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.9 import PackageDescription @@ -17,9 +17,29 @@ let package = Package( .package(url: "https://github.com/apple/swift-crypto", from: "3.0.0") ], targets: [ - .target(name: "Compute", dependencies: ["ComputeRuntime", .product(name: "Crypto", package: "swift-crypto")]), - .target(name: "ComputeRuntime"), - .executableTarget(name: "ComputeDemo", dependencies: ["Compute"]), - .testTarget(name: "ComputeTests", dependencies: ["Compute"]) + .target( + name: "Compute", + dependencies: [ + "ComputeRuntime", + .product(name: "Crypto", package: "swift-crypto") + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ] + ), + .target( + name: "ComputeRuntime", + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ] + ), + .executableTarget( + name: "ComputeDemo", + dependencies: ["Compute"] + ), + .testTarget( + name: "ComputeTests", + dependencies: ["Compute"] + ) ] ) diff --git a/README.md b/README.md index d8291e56..beb3a0bf 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ swift package init --type executable Install the Compute runtime: ```swift -.package(url: "https://github.com/swift-cloud/Compute", from: "2.19.0") +.package(url: "https://github.com/swift-cloud/Compute", from: "3.0.0") ``` Add it as a target dependency: diff --git a/Sources/Compute/Console.swift b/Sources/Compute/Console.swift index f68f43c4..4e505d8a 100644 --- a/Sources/Compute/Console.swift +++ b/Sources/Compute/Console.swift @@ -7,7 +7,7 @@ public let console = Console() -public struct Console { +public struct Console: Sendable { public var prefix: String? = nil @@ -25,19 +25,18 @@ public struct Console { } public func error(_ items: Any...) { + var errorStream = StandardErrorOutputStream() let text = items.map { String(describing: $0) }.joined(separator: " ") if let prefix = prefix { - print(prefix, text, to: &StandardErrorOutputStream.default) + print(prefix, text, to: &errorStream) } else { - print(text, to: &StandardErrorOutputStream.default) + print(text, to: &errorStream) } } } fileprivate struct StandardErrorOutputStream: TextOutputStream { - fileprivate static var `default` = StandardErrorOutputStream() - private let stderr = FileHandle.standardError func write(_ string: String) { diff --git a/Sources/Compute/Fanout/FanoutMessage.swift b/Sources/Compute/Fanout/FanoutMessage.swift index a1e90225..f751c2cf 100644 --- a/Sources/Compute/Fanout/FanoutMessage.swift +++ b/Sources/Compute/Fanout/FanoutMessage.swift @@ -74,22 +74,16 @@ extension FanoutMessage { return try decoder.decode(type, from: data()) } - public func json() throws -> Any { - return try JSONSerialization.jsonObject(with: data()) + public func json() throws -> T { + return try JSONSerialization.jsonObject(with: data()) as! T } - public func jsonObject() throws -> [String: Any] { - guard let json = try JSONSerialization.jsonObject(with: data()) as? [String: Any] else { - throw FanoutMessageError.invalidFormat - } - return json + public func jsonObject() throws -> [String: Sendable] { + return try json() } - public func jsonArray() throws -> [Any] { - guard let json = try JSONSerialization.jsonObject(with: data()) as? [Any] else { - throw FanoutMessageError.invalidFormat - } - return json + public func jsonArray() throws -> [Sendable] { + return try json() } public func data() throws -> Data { diff --git a/Sources/Compute/Fastly/FastlyEnvironment.swift b/Sources/Compute/Fastly/FastlyEnvironment.swift index 07930023..eb5bbfeb 100644 --- a/Sources/Compute/Fastly/FastlyEnvironment.swift +++ b/Sources/Compute/Fastly/FastlyEnvironment.swift @@ -29,21 +29,21 @@ extension Fastly { } extension Fastly.Environment { - public static var cacheGeneration = current["FASTLY_CACHE_GENERATION"] ?? "local" + public static let cacheGeneration = current["FASTLY_CACHE_GENERATION"] ?? "local" - public static var customerId = current["FASTLY_CUSTOMER_ID"] ?? "local" + public static let customerId = current["FASTLY_CUSTOMER_ID"] ?? "local" - public static var hostname = current["FASTLY_HOSTNAME"] ?? "localhost" + public static let hostname = current["FASTLY_HOSTNAME"] ?? "localhost" - public static var pop = current["FASTLY_POP"] ?? "local" + public static let pop = current["FASTLY_POP"] ?? "local" - public static var region = current["FASTLY_REGION"] ?? "local" + public static let region = current["FASTLY_REGION"] ?? "local" - public static var serviceId = current["FASTLY_SERVICE_ID"] ?? "local" + public static let serviceId = current["FASTLY_SERVICE_ID"] ?? "local" - public static var serviceVersion = current["FASTLY_SERVICE_VERSION"] ?? "0" + public static let serviceVersion = current["FASTLY_SERVICE_VERSION"] ?? "0" - public static var traceId = current["FASTLY_TRACE_ID"] ?? "local" + public static let traceId = current["FASTLY_TRACE_ID"] ?? "local" public static var viceroy: Bool { return hostname == "localhost" diff --git a/Sources/Compute/Fastly/FastlyStubs.swift b/Sources/Compute/Fastly/FastlyStubs.swift index 0f83baac..f961b581 100644 --- a/Sources/Compute/Fastly/FastlyStubs.swift +++ b/Sources/Compute/Fastly/FastlyStubs.swift @@ -205,7 +205,7 @@ func fastly_cache__cache_transaction_cancel(_ handle: WasiHandle) -> Int32 { fat func fastly_device__device_detection_lookup( _ user_agent: UnsafePointer!, _ user_agent_len: Int, - _ buf: UnsafeMutablePointer!, + _ buf: UnsafeMutablePointer!, _ buf_len: Int, _ nwritten: UnsafeMutablePointer! ) -> Int32 { fatalError() } diff --git a/Sources/Compute/Fetch/Fetch+Wasi.swift b/Sources/Compute/Fetch/Fetch+Wasi.swift index 2e8c9f5e..e10e64df 100644 --- a/Sources/Compute/Fetch/Fetch+Wasi.swift +++ b/Sources/Compute/Fetch/Fetch+Wasi.swift @@ -84,7 +84,7 @@ internal struct WasiFetcher: Sendable { // Register the backend if Fastly.Environment.viceroy, request.backend != "localhost" { - try registerDynamicBackend(request.backend, for: httpRequest, ssl: urlComponents.scheme == "https") + try await dynamicBackends.register(request.backend, for: httpRequest, ssl: urlComponents.scheme == "https") } // Issue async request @@ -119,25 +119,30 @@ internal struct WasiFetcher: Sendable { } } } +} - private static var dynamicBackends: Set = [] +extension WasiFetcher { + private static let dynamicBackends = DynamicBackendRepository() - private static func registerDynamicBackend(_ backend: String, for request: Fastly.Request, ssl: Bool) throws { - // Make sure we didn't already register the backend - guard dynamicBackends.contains(backend) == false else { - return - } + private actor DynamicBackendRepository { - // Attempt to register the backend - do { - try request.registerDynamicBackend(name: backend, target: backend, options: .init(ssl: ssl)) - } catch WasiStatus.unexpected { - // ignore - } catch { - throw error - } + private var state: Set = [] + + func register(_ backend: String, for request: Fastly.Request, ssl: Bool) throws { + // Make sure we didn't already register the backend + guard !state.contains(backend) else { return } - // Mark the backend as registered - dynamicBackends.insert(backend) + // Mark the backend as registered + defer { state.insert(backend) } + + // Attempt to register the backend + do { + try request.registerDynamicBackend(name: backend, target: backend, options: .init(ssl: ssl)) + } catch WasiStatus.unexpected { + // ignore + } catch { + throw error + } + } } } diff --git a/Sources/Compute/Fetch/FetchRequest.swift b/Sources/Compute/Fetch/FetchRequest.swift index a927a1c8..8049d1c0 100644 --- a/Sources/Compute/Fetch/FetchRequest.swift +++ b/Sources/Compute/Fetch/FetchRequest.swift @@ -116,12 +116,12 @@ extension FetchRequest { } public static func json(_ jsonObject: [String: Any]) throws -> Body { - let data = try JSONSerialization.data(withJSONObject: jsonObject, options: []) + let data = try JSONSerialization.data(withJSONObject: jsonObject) return Body.json(data) } public static func json(_ jsonArray: [Any]) throws -> Body { - let data = try JSONSerialization.data(withJSONObject: jsonArray, options: []) + let data = try JSONSerialization.data(withJSONObject: jsonArray) return Body.json(data) } diff --git a/Sources/Compute/Fetch/FetchResponse.swift b/Sources/Compute/Fetch/FetchResponse.swift index dbcfba91..c44c72c0 100644 --- a/Sources/Compute/Fetch/FetchResponse.swift +++ b/Sources/Compute/Fetch/FetchResponse.swift @@ -39,15 +39,15 @@ extension FetchResponse { return try await body.decode(type, decoder: decoder) } - public func json() async throws -> Any { + public func json() async throws -> T { return try await body.json() } - public func jsonObject() async throws -> [String: Any] { + public func jsonObject() async throws -> [String: Sendable] { return try await body.jsonObject() } - public func jsonArray() async throws -> [Any] { + public func jsonArray() async throws -> [Sendable] { return try await body.jsonArray() } diff --git a/Sources/Compute/JWT/JWT.swift b/Sources/Compute/JWT/JWT.swift index 9b0bd708..9045aff3 100644 --- a/Sources/Compute/JWT/JWT.swift +++ b/Sources/Compute/JWT/JWT.swift @@ -54,7 +54,7 @@ public struct JWT: Sendable { } public init( - claims: [String: Any], + claims: [String: Sendable], secret: String, algorithm: Algorithm = .hs256, issuedAt: Date = .init(), @@ -63,12 +63,12 @@ public struct JWT: Sendable { subject: String? = nil, identifier: String? = nil ) throws { - let header: [String: Any] = [ + let header: [String: Sendable] = [ "alg": algorithm.rawValue, "typ": "JWT" ] - var properties: [String: Any] = [ + var properties: [String: Sendable] = [ "iat": floor(issuedAt.timeIntervalSince1970) ] @@ -192,9 +192,9 @@ extension JWT { } } -private func decodeJWTPart(_ value: String) throws -> [String: Any] { +private func decodeJWTPart(_ value: String) throws -> [String: Sendable] { let bodyData = try base64UrlDecode(value) - guard let json = try JSONSerialization.jsonObject(with: bodyData, options: []) as? [String: Any] else { + guard let json = try JSONSerialization.jsonObject(with: bodyData) as? [String: Sendable] else { throw JWTError.invalidJSON } return json diff --git a/Sources/Compute/KVStore.swift b/Sources/Compute/KVStore.swift index 9d49e74e..382dc7ba 100644 --- a/Sources/Compute/KVStore.swift +++ b/Sources/Compute/KVStore.swift @@ -71,12 +71,12 @@ extension KVStore { } public func put(_ key: String, jsonObject: [String: Any]) async throws { - let data = try JSONSerialization.data(withJSONObject: jsonObject, options: []) + let data = try JSONSerialization.data(withJSONObject: jsonObject) try await put(key, data: data) } public func put(_ key: String, jsonArray: [Any]) async throws { - let data = try JSONSerialization.data(withJSONObject: jsonArray, options: []) + let data = try JSONSerialization.data(withJSONObject: jsonArray) try await put(key, data: data) } diff --git a/Sources/Compute/ReadableBody/ReadableBody+Data.swift b/Sources/Compute/ReadableBody/ReadableBody+Data.swift index 7384a0b3..430c23fc 100644 --- a/Sources/Compute/ReadableBody/ReadableBody+Data.swift +++ b/Sources/Compute/ReadableBody/ReadableBody+Data.swift @@ -37,16 +37,16 @@ extension ReadableDataBody { return try decoder.decode(type, from: data) } - func json() async throws -> Sendable { - return try JSONSerialization.jsonObject(with: data) + func json() async throws -> T { + return try JSONSerialization.jsonObject(with: data) as! T } func jsonObject() async throws -> [String : Sendable] { - return try JSONSerialization.jsonObject(with: data) as! [String: Any] + return try await json() } func jsonArray() async throws -> [Sendable] { - return try JSONSerialization.jsonObject(with: data) as! [Any] + return try await json() } func formValues() async throws -> HTTPSearchParams { diff --git a/Sources/Compute/ReadableBody/ReadableBody+Wasi.swift b/Sources/Compute/ReadableBody/ReadableBody+Wasi.swift index 5ee6ceb0..9fcd437f 100644 --- a/Sources/Compute/ReadableBody/ReadableBody+Wasi.swift +++ b/Sources/Compute/ReadableBody/ReadableBody+Wasi.swift @@ -49,18 +49,17 @@ extension ReadableWasiBody { return try decoder.decode(type, from: data) } - func json() async throws -> Sendable { + func json() async throws -> T { let data = try await data() - let dict = try JSONSerialization.jsonObject(with: data, options: []) - return dict + return try JSONSerialization.jsonObject(with: data) as! T } func jsonObject() async throws -> [String: Sendable] { - return try await json() as! [String: Sendable] + return try await json() } func jsonArray() async throws -> [Sendable] { - return try await json() as! [Sendable] + return try await json() } func formValues() async throws -> HTTPSearchParams { diff --git a/Sources/Compute/ReadableBody/ReadableBody.swift b/Sources/Compute/ReadableBody/ReadableBody.swift index bab3a8ab..d53b880a 100644 --- a/Sources/Compute/ReadableBody/ReadableBody.swift +++ b/Sources/Compute/ReadableBody/ReadableBody.swift @@ -17,7 +17,7 @@ public protocol ReadableBody: Actor, Sendable { func decode(_ type: T.Type, decoder: JSONDecoder) async throws -> T where T: Decodable - func json() async throws -> Sendable + func json() async throws -> T func jsonObject() async throws -> [String: Sendable] diff --git a/Sources/ComputeRuntime/include/ComputeRuntime.h b/Sources/ComputeRuntime/include/ComputeRuntime.h index 13211d87..446d6df5 100644 --- a/Sources/ComputeRuntime/include/ComputeRuntime.h +++ b/Sources/ComputeRuntime/include/ComputeRuntime.h @@ -338,7 +338,7 @@ int fastly_cache__cache_get_body(WasiHandle handle, uint32_t options_mask, /* FASTLY_DEVICE */ WASM_IMPORT("fastly_device_detection", "lookup") -int fastly_device__device_detection_lookup(const char *user_agent, size_t user_agent_len, const char *buf, +int fastly_device__device_detection_lookup(const char *user_agent, size_t user_agent_len, uint8_t *buf, size_t buf_len, size_t *nwritten); #pragma GCC diagnostic pop