Files
swift-mirror/test/Concurrency/async_task_groups.swift
Karoy Lorentey 47956908b7 [Concurrency] SwiftStdlib 5.5 ⟹ SwiftStdlib 5.1 (usages)
The concurrency runtime now deploys back to macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, which corresponds to the 5.1 release of the stdlib.

Adjust macro usages accordingly.
2021-10-28 14:36:36 -07:00

223 lines
5.7 KiB
Swift

// RUN: %target-typecheck-verify-swift -disable-availability-checking
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: libdispatch
@available(SwiftStdlib 5.1, *)
func asyncFunc() async -> Int { 42 }
@available(SwiftStdlib 5.1, *)
func asyncThrowsFunc() async throws -> Int { 42 }
@available(SwiftStdlib 5.1, *)
func asyncThrowsOnCancel() async throws -> Int {
// terrible suspend-spin-loop -- do not do this
// only for purposes of demonstration
while Task.isCancelled {
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
throw CancellationError()
}
@available(SwiftStdlib 5.1, *)
func test_taskGroup_add() async throws -> Int {
try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask {
await asyncFunc()
}
group.addTask {
await asyncFunc()
}
var sum = 0
while let v = try await group.next() {
sum += v
}
return sum
} // implicitly awaits
}
// ==== ------------------------------------------------------------------------
// MARK: Example group Usages
struct Boom: Error {}
@available(SwiftStdlib 5.1, *)
func work() async -> Int { 42 }
@available(SwiftStdlib 5.1, *)
func boom() async throws -> Int { throw Boom() }
@available(SwiftStdlib 5.1, *)
func first_allMustSucceed() async throws {
let first: Int = try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask { await work() }
group.addTask { await work() }
group.addTask { try await boom() }
if let first = try await group.next() {
return first
} else {
fatalError("Should never happen, we either throw, or get a result from any of the tasks")
}
// implicitly await: boom
}
_ = first
// Expected: re-thrown Boom
}
@available(SwiftStdlib 5.1, *)
func first_ignoreFailures() async throws {
@Sendable func work() async -> Int { 42 }
@Sendable func boom() async throws -> Int { throw Boom() }
let first: Int = try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask { await work() }
group.addTask { await work() }
group.addTask {
do {
return try await boom()
} catch {
return 0 // TODO: until try? await works properly
}
}
var result: Int = 0
while let v = try await group.next() {
result = v
if result != 0 {
break
}
}
return result
}
_ = first
// Expected: re-thrown Boom
}
// ==== ------------------------------------------------------------------------
// MARK: Advanced Custom Task Group Usage
@available(SwiftStdlib 5.1, *)
func test_taskGroup_quorum_thenCancel() async {
// imitates a typical "gather quorum" routine that is typical in distributed systems programming
enum Vote {
case yay
case nay
}
struct Follower: Sendable {
init(_ name: String) {}
func vote() async throws -> Vote {
// "randomly" vote yes or no
return .yay
}
}
/// Performs a simple quorum vote among the followers.
///
/// - Returns: `true` iff `N/2 + 1` followers return `.yay`, `false` otherwise.
func gatherQuorum(followers: [Follower]) async -> Bool {
try! await withThrowingTaskGroup(of: Vote.self) { group in
for follower in followers {
group.addTask { try await follower.vote() }
}
defer {
group.cancelAll()
}
var yays: Int = 0
var nays: Int = 0
let quorum = Int(followers.count / 2) + 1
while let vote = try await group.next() {
switch vote {
case .yay:
yays += 1
if yays >= quorum {
// cancel all remaining voters, we already reached quorum
return true
}
case .nay:
nays += 1
if nays >= quorum {
return false
}
}
}
return false
}
}
_ = await gatherQuorum(followers: [Follower("A"), Follower("B"), Follower("C")])
}
// FIXME: this is a workaround since (A, B) today isn't inferred to be Sendable
// and causes an error, but should be a warning (this year at least)
@available(SwiftStdlib 5.1, *)
struct SendableTuple2<A: Sendable, B: Sendable>: Sendable {
let first: A
let second: B
init(_ first: A, _ second: B) {
self.first = first
self.second = second
}
}
@available(SwiftStdlib 5.1, *)
extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendable {
/// Just another example of how one might use task groups.
func map<T: Sendable>(
parallelism requestedParallelism: Int? = nil/*system default*/,
// ordered: Bool = true, /
_ transform: @Sendable (Element) async throws -> T
) async throws -> [T] { // TODO: can't use rethrows here, maybe that's just life though; rdar://71479187 (rethrows is a bit limiting with async functions that use task groups)
let defaultParallelism = 2
let parallelism = requestedParallelism ?? defaultParallelism
let n = self.count
if n == 0 {
return []
}
return try await withThrowingTaskGroup(of: SendableTuple2<Int, T>.self) { group in
var result = ContiguousArray<T>()
result.reserveCapacity(n)
var i = self.startIndex
var submitted = 0
func submitNext() async throws {
group.addTask { [submitted,i] in
let value = try await transform(self[i])
return SendableTuple2(submitted, value)
}
submitted += 1
formIndex(after: &i)
}
// submit first initial tasks
for _ in 0..<parallelism {
try await submitNext()
}
while let tuple = try await group.next() {
let index = tuple.first
let taskResult = tuple.second
result[index] = taskResult
try Task.checkCancellation()
try await submitNext()
}
assert(result.count == n)
return Array(result)
}
}
}