Use `withThrowingTaskGroup` within Actor leads to `Non-sendable type '(inout ThrowingTaskGroup<Void, any Error>) async throws -> ()'` compilation warning

When using compilation flag -warn-concurrency and -enable-actor-data-race-checks (possibly without? Not tried...)

actor ConnectorActor {
    struct Config {}
    private let config: Config
    init(config: Config) {
        self.config = config
    }
    func connect() async {
        await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask {
                /* ... */
            }
         
        }
    }
}
Non-sendable type '(inout ThrowingTaskGroup<Void, any Error>) async throws -> ()' exiting actor-isolated context in call to non-isolated global function 'withThrowingTaskGroup(of:returning:body:)' cannot cross actor boundary

But if I wrap the withThrowingTaskGroup within a Task the warning goes away, like so:

    func connect() async {
        Task {
            await withThrowingTaskGroup(of: Void.self) { group in
                group.addTask {
                    /* ... */
                }
                
            }
        }
    }

What is the best practices here?

3 Likes

This was recently fixed in top-of-tree:

You should be able to ignore the warning for now.

2 Likes

With Xcode 14.1 Beta 2 (swiftlang-5.7.1.131.4 clang-1400.0.29.51), I see the same warning for reduce(into:) when I use it in MainActor as below.
Is this related and I should ignore it for now or this is legit?

I have seen this warning to, and it felt a bit too overly strict

1 Like

With the release of Xcode 15.3, this warning now occurs by default. I'm not sure how to fix this.

actor MyActor {
  func foo() async throws -> [String] {
    try await withThrowingTaskGroup(of: String.self) { group in
      group.addTask { await self.bar() }

      return try await group.reduce(into: [String]()) { (result, elem) in  // Passing argument of non-sendable type 'ThrowingTaskGroup<String, any Error>' outside of actor-isolated context may introduce data races
        result.append(elem)
      }
    }
  }

  func bar() async -> String { "" }
}
2 Likes

Don't use reduce directly onto group. Accumulate the results, then reduce. Though you don't really seem to be reducing anything here anyway.

We’re aware of the warning and will be fixing it with the new currently under review isolation controls.

2 Likes

Why?

1 Like

Any way we can track this issue status? Is there any related Github issue or pr link?

1 Like

I assume this counsel is just a temporary “this is how you can avoid this warning for now, until it is fixed”, and not intended as anything more than that. Is that correct?

1 Like

Yes, it's just a workaround.

3 Likes

Whether using reduce() or not, neither way build with Swift 6, as of Xcode 16 beta 2.

For reduce(), it's "Sending 'group' risks causing data races" and for for-in, it's "Sending '$value$generator' risks causing data races".

The fix is a combination of very recent changes, I don't expect it to be in beta 2 but we expect this error to go away in subsequent releases.

I can't comment on exact release dates though, sorry about that.

5 Likes
actor MyActor {
  func iterate<Sequence>(over sequence: Sequence) async rethrows where Sequence : AsyncSequence, Sequence.Element : Sendable {
    //  Sending '$ns$generator' risks causing data races
    for try await ns in sequence {
      print(ns)
    }
  }
}

I'm not sure this is a problem specifically with TaskGroup… I see a similar problem with an arbitrary AsyncSequence.

If I'm building from a new OS… I have this option from SE-0420:

actor MyActor {
  func iterate<Sequence>(over sequence: Sequence) async rethrows where Sequence : AsyncSequence, Sequence.Element : Sendable {
    var iterator = sequence.makeAsyncIterator()
    while let ns = try await iterator.next(isolation: #isolation) {
      print(ns)
    }
  }
}

This silences the error… but I still would like to support legacy OS versions (while building from the 6.0 toolchain).

@ktoso Would you know if we have any legit workarounds for that from Xcode_16_beta_2 that would also deploy back to the last OS releases (other than potentially not building from Swift 6 Strict Concurrency Checking)?

No longer errors for me from Xcode_16_beta_3.

As of Xcode 16 beta 3, for in group no longer produces the warning, but group.reduce() still does.

With Xcode 16 beta 5, group.reduce() still produces the same warning :disappointed:

@MainActor struct S {
  func f() async {
    await withTaskGroup(of: Int.self) { group in
      let arr = await group.reduce(into: [Int]()) { $0.append($1) }  // ❌ Sending 'group' risks causing data races
    }
  }
}

Heh, this made me realize that none of those APIs adopted #isolation and indeed will be getting such warnings... We'll have to re-evaluate more async sequence APIs and if they should take #isolation.

Thanks for the ping on it, I've added it a list of things to look into.

5 Likes

For visibility, this is still the case with Xcode 16 beta 6. :disappointed:

1 Like