From e88bf8895b577c060096569fd1b95d0fb204fb28 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Thu, 24 Apr 2025 17:32:44 +0200 Subject: [PATCH 1/8] - --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index e0ea36f..a435f5a 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -6.0 +6.1 From 9a5c454cc4cce82c2ca806f40a596298540ae10c Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Thu, 24 Apr 2025 17:42:10 +0200 Subject: [PATCH 2/8] - --- .github/workflows/pull-request.yml | 12 ++++++------ .swift-version | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f441094..041d95b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -67,21 +67,21 @@ jobs: id: destination run: | case "${{ matrix.platform }}" in - ios) - destination="platform=iOS Simulator,name=iPhone 16 Pro Max,OS=latest" - ;; maccatalyst) destination="platform=macOS,variant=Mac Catalyst" ;; + ios) + destination="platform=iOS Simulator,name=iPhone 16 Pro Max,OS=latest" + ;; tvos) destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest" ;; - visionos) - destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=latest" - ;; watchos) destination="platform=watchOS Simulator,name=Apple Watch Series 10 (46mm),OS=latest" ;; + visionos) + destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=latest" + ;; *) echo "Unknown platform: ${{ matrix.platform }}" exit 1 diff --git a/.swift-version b/.swift-version index a435f5a..e8f1734 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -6.1 +6.1 \ No newline at end of file From d6f5879096c2f9d43b4e1b5b9682dd1acc533fee Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 13:46:50 +0200 Subject: [PATCH 3/8] - --- .../PrincipleConcurrency/TaskTimeLimit.swift | 63 +++++++++++++------ .../CustomActor.swift | 13 ++++ .../TaskTimeLimitTests.swift | 20 ++++++ 3 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 Tests/PrincipleConcurrencyTests/CustomActor.swift diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index 4bede52..602f69a 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -9,9 +9,11 @@ // Copyright © 2024 Philipp Gabriel. Original code licensed under MIT. // -private enum TaskTimeLimit { +internal enum TaskTimeLimit { - enum Event { + typealias Operation = @isolated(any) () async throws -> Success + + fileprivate enum Event { case taskFinished(Result) case parentTaskCancelled @@ -25,29 +27,16 @@ internal func withTimeLimit( // swiftlint:disable:t tolerance: C.Instant.Duration?, clock: C, priority: TaskPriority?, - isolation: isolated (any Actor)?, - operation: sending @escaping @isolated(any) () async throws -> Success + isolation callerIsolation: isolated (any Actor)?, + operation: sending @escaping TaskTimeLimit.Operation ) async throws -> Success { var transfer = SingleUseTransfer(operation) let result = await withTaskGroup( - of: TaskTimeLimit.Event.self, + of: TaskTimeLimit.Event.self, returning: Result.self, - isolation: isolation, + isolation: callerIsolation, body: { group in - var transfer = transfer.take() - - group.addTask(priority: priority) { - do { - // Review after closure isolation control gets implemented - // https://forums.swift.org/t/closure-isolation-control/70378 - let success = try await transfer.finalize()() - return .taskFinished(.success(success)) - } catch { - return .taskFinished(.failure(error)) - } - } - group.addTask(priority: priority) { do { try await Task.sleep(until: deadline, tolerance: tolerance, clock: clock) @@ -57,6 +46,27 @@ internal func withTimeLimit( // swiftlint:disable:t } } + do { + nonisolated(unsafe) var unsafeGroup = group + defer { group = consume unsafeGroup } + + await unpackOperation( + transfer.finalize(), + callerIsolation: callerIsolation, + transform: { operation, operationIsolation in + unsafeGroup.addTask(priority: priority) { + do { + _ = operationIsolation + let success = try await operation() + return .taskFinished(.success(success)) + } catch { + return .taskFinished(.failure(error)) + } + } + } + ) + } + defer { group.cancelAll() } @@ -78,3 +88,18 @@ internal func withTimeLimit( // swiftlint:disable:t return try result.get() } + +private func unpackOperation( + _ operation: sending @escaping TaskTimeLimit.Operation, + callerIsolation _: isolated (any Actor)?, + transform: (sending @escaping TaskTimeLimit.Operation, isolated (any Actor)?) -> sending R +) async -> sending R { + // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md + // https://forums.swift.org/t/closure-isolation-control/70378 + + // Currently operationIsolation does not affect actual isolation of child task. + // https://forums.swift.org/t/explicitly-captured-isolated-parameter-does-not-change-isolation-of-sendable-sending-closures/79502 + let operationIsolation = extractIsolation(operation) + + return await transform(operation, operationIsolation) +} diff --git a/Tests/PrincipleConcurrencyTests/CustomActor.swift b/Tests/PrincipleConcurrencyTests/CustomActor.swift new file mode 100644 index 0000000..12445cf --- /dev/null +++ b/Tests/PrincipleConcurrencyTests/CustomActor.swift @@ -0,0 +1,13 @@ +// +// CustomActor.swift +// Principle +// +// Created by Kamil Strzelecki on 24/04/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@globalActor +internal actor CustomActor { + + static let shared = CustomActor() +} diff --git a/Tests/PrincipleConcurrencyTests/TaskTimeLimitTests.swift b/Tests/PrincipleConcurrencyTests/TaskTimeLimitTests.swift index f333706..f57511f 100644 --- a/Tests/PrincipleConcurrencyTests/TaskTimeLimitTests.swift +++ b/Tests/PrincipleConcurrencyTests/TaskTimeLimitTests.swift @@ -54,6 +54,16 @@ internal struct TaskTimeLimitTests { try await task.value } } + + @Test + func testIsolation() async throws { + let task = Task { @CustomActor in + try await withDeadline(until: .now + .seconds(1)) { + CustomActor.shared.assertIsolated() + } + } + try await task.value + } } struct Timeout { @@ -99,6 +109,16 @@ internal struct TaskTimeLimitTests { try await task.value } } + + @Test + func testIsolation() async throws { + let task = Task { @CustomActor in + try await withTimeout(.seconds(1)) { + CustomActor.shared.assertIsolated() + } + } + try await task.value + } } } From 687b85bc23c3b561eaf6fd0d6f7fcc6368b62890 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 13:46:50 +0200 Subject: [PATCH 4/8] [SwiftFormat] Applied formatting --- Sources/PrincipleConcurrency/TaskTimeLimit.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index 602f69a..24ce368 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -92,7 +92,7 @@ internal func withTimeLimit( // swiftlint:disable:t private func unpackOperation( _ operation: sending @escaping TaskTimeLimit.Operation, callerIsolation _: isolated (any Actor)?, - transform: (sending @escaping TaskTimeLimit.Operation, isolated (any Actor)?) -> sending R + transform: (sending @escaping TaskTimeLimit.Operation, isolated(any Actor)?) -> sending R ) async -> sending R { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md // https://forums.swift.org/t/closure-isolation-control/70378 From 01301a573a5073b13832bc5fa02969c24acbe98d Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 14:07:28 +0200 Subject: [PATCH 5/8] - --- Sources/PrincipleConcurrency/TaskTimeLimit.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index 24ce368..a364ac5 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -56,6 +56,8 @@ internal func withTimeLimit( // swiftlint:disable:t transform: { operation, operationIsolation in unsafeGroup.addTask(priority: priority) { do { + // NOTE: #isolation != operationIsolation + // https://forums.swift.org/t/explicitly-captured-isolated-parameter-does-not-change-isolation-of-sendable-sending-closures/79502 _ = operationIsolation let success = try await operation() return .taskFinished(.success(success)) @@ -96,10 +98,6 @@ private func unpackOperation( ) async -> sending R { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md // https://forums.swift.org/t/closure-isolation-control/70378 - - // Currently operationIsolation does not affect actual isolation of child task. - // https://forums.swift.org/t/explicitly-captured-isolated-parameter-does-not-change-isolation-of-sendable-sending-closures/79502 let operationIsolation = extractIsolation(operation) - return await transform(operation, operationIsolation) } From 55ad882e9059b1f0d5823b460b5733328e15f217 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 14:59:14 +0200 Subject: [PATCH 6/8] - --- .../DeadlineExceededError.swift | 4 +- .../PrincipleConcurrency/TaskTimeLimit.swift | 53 ++++++------------- .../PrincipleConcurrency/TimeoutError.swift | 4 +- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/Sources/PrincipleConcurrency/DeadlineExceededError.swift b/Sources/PrincipleConcurrency/DeadlineExceededError.swift index 1edb2b2..21966ce 100644 --- a/Sources/PrincipleConcurrency/DeadlineExceededError.swift +++ b/Sources/PrincipleConcurrency/DeadlineExceededError.swift @@ -33,7 +33,7 @@ public func withDeadline( clock: C, priority: TaskPriority? = nil, isolation: isolated (any Actor)? = #isolation, - @_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success + @_inheritActorContext @_implicitSelfCapture operation: sending @escaping () async throws -> Success ) async throws -> Success { try await withTimeLimit( throwing: DeadlineExceededError(), @@ -65,7 +65,7 @@ public func withDeadline( tolerance: Duration? = nil, priority: TaskPriority? = nil, isolation: isolated (any Actor)? = #isolation, - @_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success + @_inheritActorContext @_implicitSelfCapture operation: sending @escaping () async throws -> Success ) async throws -> Success { try await withDeadline( until: deadline, diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index a364ac5..bec1a39 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -9,11 +9,9 @@ // Copyright © 2024 Philipp Gabriel. Original code licensed under MIT. // -internal enum TaskTimeLimit { +private enum TaskTimeLimit { - typealias Operation = @isolated(any) () async throws -> Success - - fileprivate enum Event { + enum Event { case taskFinished(Result) case parentTaskCancelled @@ -28,7 +26,7 @@ internal func withTimeLimit( // swiftlint:disable:t clock: C, priority: TaskPriority?, isolation callerIsolation: isolated (any Actor)?, - operation: sending @escaping TaskTimeLimit.Operation + operation: sending @escaping () async throws -> Success ) async throws -> Success { var transfer = SingleUseTransfer(operation) @@ -37,6 +35,8 @@ internal func withTimeLimit( // swiftlint:disable:t returning: Result.self, isolation: callerIsolation, body: { group in + var transfer = transfer.take() + group.addTask(priority: priority) { do { try await Task.sleep(until: deadline, tolerance: tolerance, clock: clock) @@ -46,27 +46,17 @@ internal func withTimeLimit( // swiftlint:disable:t } } - do { - nonisolated(unsafe) var unsafeGroup = group - defer { group = consume unsafeGroup } - - await unpackOperation( - transfer.finalize(), - callerIsolation: callerIsolation, - transform: { operation, operationIsolation in - unsafeGroup.addTask(priority: priority) { - do { - // NOTE: #isolation != operationIsolation - // https://forums.swift.org/t/explicitly-captured-isolated-parameter-does-not-change-isolation-of-sendable-sending-closures/79502 - _ = operationIsolation - let success = try await operation() - return .taskFinished(.success(success)) - } catch { - return .taskFinished(.failure(error)) - } - } - } - ) + group.addTask(priority: priority) { + do { + // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md + // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0472-task-start-synchronously-on-caller-context.md + // https://forums.swift.org/t/explicitly-captured-isolated-parameter-does-not-change-isolation-of-sendable-sending-closures/79502 + // https://forums.swift.org/t/closure-isolation-control/70378 + let success = try await transfer.finalize()() + return .taskFinished(.success(success)) + } catch { + return .taskFinished(.failure(error)) + } } defer { @@ -90,14 +80,3 @@ internal func withTimeLimit( // swiftlint:disable:t return try result.get() } - -private func unpackOperation( - _ operation: sending @escaping TaskTimeLimit.Operation, - callerIsolation _: isolated (any Actor)?, - transform: (sending @escaping TaskTimeLimit.Operation, isolated(any Actor)?) -> sending R -) async -> sending R { - // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md - // https://forums.swift.org/t/closure-isolation-control/70378 - let operationIsolation = extractIsolation(operation) - return await transform(operation, operationIsolation) -} diff --git a/Sources/PrincipleConcurrency/TimeoutError.swift b/Sources/PrincipleConcurrency/TimeoutError.swift index 136785c..bf039c2 100644 --- a/Sources/PrincipleConcurrency/TimeoutError.swift +++ b/Sources/PrincipleConcurrency/TimeoutError.swift @@ -33,7 +33,7 @@ public func withTimeout( clock: C, priority: TaskPriority? = nil, isolation: isolated (any Actor)? = #isolation, - @_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success + @_inheritActorContext @_implicitSelfCapture operation: sending @escaping () async throws -> Success ) async throws -> Success { try await withTimeLimit( throwing: TimeoutError(), @@ -65,7 +65,7 @@ public func withTimeout( tolerance: Duration? = nil, priority: TaskPriority? = nil, isolation: isolated (any Actor)? = #isolation, - @_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success + @_inheritActorContext @_implicitSelfCapture operation: sending @escaping () async throws -> Success ) async throws -> Success { try await withTimeout( duration, From cbd3afa996ea2efdf456ea4e71c52ad22d5033dc Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 15:02:59 +0200 Subject: [PATCH 7/8] - --- Sources/PrincipleConcurrency/TaskTimeLimit.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index bec1a39..420fcc7 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -25,7 +25,7 @@ internal func withTimeLimit( // swiftlint:disable:t tolerance: C.Instant.Duration?, clock: C, priority: TaskPriority?, - isolation callerIsolation: isolated (any Actor)?, + isolation: isolated (any Actor)?, operation: sending @escaping () async throws -> Success ) async throws -> Success { var transfer = SingleUseTransfer(operation) @@ -33,7 +33,7 @@ internal func withTimeLimit( // swiftlint:disable:t let result = await withTaskGroup( of: TaskTimeLimit.Event.self, returning: Result.self, - isolation: callerIsolation, + isolation: isolation, body: { group in var transfer = transfer.take() From b58e8d2019eea898c8dd9ef834072a403dfb4234 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 25 Apr 2025 15:03:39 +0200 Subject: [PATCH 8/8] - --- .../PrincipleConcurrency/TaskTimeLimit.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/PrincipleConcurrency/TaskTimeLimit.swift b/Sources/PrincipleConcurrency/TaskTimeLimit.swift index 420fcc7..b3f7a66 100644 --- a/Sources/PrincipleConcurrency/TaskTimeLimit.swift +++ b/Sources/PrincipleConcurrency/TaskTimeLimit.swift @@ -37,15 +37,6 @@ internal func withTimeLimit( // swiftlint:disable:t body: { group in var transfer = transfer.take() - group.addTask(priority: priority) { - do { - try await Task.sleep(until: deadline, tolerance: tolerance, clock: clock) - return .timeLimitExceeded - } catch { - return .parentTaskCancelled - } - } - group.addTask(priority: priority) { do { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md @@ -59,6 +50,15 @@ internal func withTimeLimit( // swiftlint:disable:t } } + group.addTask(priority: priority) { + do { + try await Task.sleep(until: deadline, tolerance: tolerance, clock: clock) + return .timeLimitExceeded + } catch { + return .parentTaskCancelled + } + } + defer { group.cancelAll() }