I'm trying to simulate DiscardingThrowingTaskgGroup
using ThrowingTaskgGroup
and actor isolation
.
And I end up with Concurrency Compiler error that looks odd to me.
This code does compile and ThrowingTaskGroup
is protected by actor isolation.
/// Empty actor to isolate `ThrowingTaskGroup` to simulate DiscardingTaskGroup
@usableFromInline
actor SafetyRegion {
private var isFinished = false
private var continuation: UnsafeContinuation<Void,Never>? = nil
@usableFromInline
init() {
}
@usableFromInline
func markDone() {
guard !isFinished else { return }
isFinished = true
continuation?.resume()
continuation = nil
}
@usableFromInline
func hold() async {
await withUnsafeContinuation {
if isFinished {
$0.resume()
} else {
if let old = self.continuation {
assertionFailure("received suspend more than once!")
old.resume()
}
self.continuation = $0
}
}
}
}
extension ThrowingTaskGroup where ChildTaskResult == Void, Failure == any Error {
/// work around for simulating Discarding TaskGroup
///
/// TaskGroup is protected by the actor isolation
/// - important: always call TaskGroup api while holding isolation
@usableFromInline
internal mutating func simulateDiscarding(
isolation actor: isolated (SafetyRegion),
body: (isolated SafetyRegion, inout Self) async throws -> Void
) async throws {
addTask {
/// keep at least one child task alive
/// so that subTask won't return
await actor.hold()
}
/// drain all the finished or failed Task
async let subTask:Void = {
while let _ = try await next(isolation: actor) {
}
}()
// wrap with do block so that `defer` pops before waiting subTask
do {
/// release suspending Task
defer { actor.markDone() }
/// wrap the mutable TaskGroup with actor isolation
try await body(actor, &self)
}
try await subTask
}
}
However, below two code are not compiled on swift 6.
func simulateDiscardingThrowingTaskGroup<T:Actor>(
isolation actor: isolated T,
body: (isolated T, inout ThrowingTaskGroup<Void, any Error>) async throws -> Void
) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
let holder = SafetyRegion()
group.addTask {
await holder.hold()
}
async let drainTask:Void = {
/// Error:Sending 'group' risks causing data races
///'actor'-isolated 'group' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
while let _ = try await group.next(isolation: actor) {
}
}()
do {
///Error:Sending 'body' risks causing data races
/// description: 'actor'-isolated 'body' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
///Error: Sending 'group' risks causing data races
/// description: Sending 'actor'-isolated 'group' to nonisolated callee risks causing data races between nonisolated and 'actor'-isolated uses
try await body(actor, &group)
await holder.markDone()
} catch {
await holder.markDone()
throw error
}
try await drainTask
}
}
//Same error like above
extension Actor {
func simuateDiscardingThrowingTaskGroup(
_ body: (isolated Self, inout ThrowingTaskGroup<Void, any Error>) async throws -> Void
) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
let holder = SafetyRegion()
group.addTask {
await holder.hold()
}
async let drainTask:Void = {
/// Error:Sending 'group' risks causing data races
///'actor'-isolated 'group' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
while let _ = try await group.next(isolation: self) {
}
}()
do {
///Error:Sending 'body' risks causing data races
/// description: 'actor'-isolated 'body' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
///Error: Sending 'group' risks causing data races
/// description: Sending 'actor'-isolated 'group' to nonisolated callee risks causing data races between nonisolated and 'actor'-isolated uses
try await body(self, &group)
await holder.markDone()
} catch {
await holder.markDone()
throw error
}
try await drainTask
}
}
}
Are these code blocks, actually different?
- Why does writing extension on
ThrowignTaskGroup
does not require Sendable body closure but, others does? - What is the possible data race problem that the compiler is detecting?