withThrowingTaskGroup doesn't (re)throw the error

I have quite simple usage of the task group:

func blah() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask {
                try await self.connection.sendMessage(name: route.rawValue)
            }
            group.addTask {
                try await Task.sleep(for: .seconds(2))
                try Task.checkCancellation()
                throw TaskError.timedOut
            }
            try await group.next()
            group.cancelAll()
            
        }
}

I'd expect that by calling try await blah() I will get the exception if the "timeout" group task will finish first, but NOTHING happens. The method just returns (edit: not true, actually it doesn't return). The sendMessage is causing it, because if I replace it with Task.sleep (for 10s for example) then everything is fine. But the method is just a wrapper for xpc calls.
Any ideas what can be wrong?

Would you mind debugging or printing the exact execution flow -- what task does what, in what order, so we could observe the details of execution? It's hard to say from your description if there's a confusion on behavior or what specifically.

Yes, the group should re-throw the first error encountered. Then ignore the second one.

Cześć!

Sure thing! :)
That's the implementation from the experiment:

func sendMessage(_ route: XPCRoute) async throws  {
        let id = UUID()
        logger.debug("\(id, privacy: .public) sendMessage began")
        do {
            try await withThrowingTaskGroup(of: Void.self) { group in
                group.addTask {
                    self.logger.debug("\(id, privacy: .public) Sending message with route: \(route.rawValue)")
                    try await self.connection.sendMessage(name: route.rawValue)
                }
                group.addTask {
                    self.logger.debug("\(id, privacy: .public) I will sleep for 2 seconds")
                    try? await Task.sleep(for: .seconds(2))
                    try Task.checkCancellation()
                    throw TaskError.timedOut
                }
                do {
                    try await group.next()
                    logger.debug("\(id, privacy: .public) Did receive a message")
                    group.cancelAll()
                } catch {
                    self.logger.debug("\(id, privacy: .public) The first task has thrown an error: \(error.localizedDescription, privacy: .public)")
                    throw error
                }
            }
        } catch {
            self.logger.debug("\(id, privacy: .public) Expected error happened")
            throw error
        }
        logger.debug("\(id, privacy: .public) sendMessage ended")
    }

That's its output:

It looks like the withThrowingTaskGroup never returned.

Hmmm, the sleep should have returned and the second task completed... The group automatically waits for all tasks, which is why it's waiting here but it should have returned AFAICS.

What SDK version are you using?

I would recommend adding the cancelAll() on all return paths btw, because now you will ALWAYS keep waiting for the 2 seconds, even if the error was already thrown. Worth an experiment if this changes anything here too.