mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
222 lines
6.0 KiB
Swift
222 lines
6.0 KiB
Swift
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
|
|
|
|
// REQUIRES: executable_test
|
|
// REQUIRES: concurrency
|
|
// REQUIRES: libdispatch
|
|
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func asyncFunc() async -> Int { 42 }
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func asyncThrowsFunc() async throws -> Int { 42 }
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func asyncThrowsOnCancel() async throws -> Int {
|
|
// terrible suspend-spin-loop -- do not do this
|
|
// only for purposes of demonstration
|
|
while Task.isCancelled {
|
|
await Task.sleep(1_000_000_000)
|
|
}
|
|
|
|
throw Task.CancellationError()
|
|
}
|
|
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func test_taskGroup_add() async throws -> Int {
|
|
try await withThrowingTaskGroup(of: Int.self) { group in
|
|
group.spawn {
|
|
await asyncFunc()
|
|
}
|
|
|
|
group.spawn {
|
|
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(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func work() async -> Int { 42 }
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func boom() async throws -> Int { throw Boom() }
|
|
|
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
func first_allMustSucceed() async throws {
|
|
|
|
let first: Int = try await withThrowingTaskGroup(of: Int.self) { group in
|
|
group.spawn { await work() }
|
|
group.spawn { await work() }
|
|
group.spawn { 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(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
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.spawn { await work() }
|
|
group.spawn { await work() }
|
|
group.spawn {
|
|
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(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
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.spawn { 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(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
|
struct SendableTuple2<A: Sendable, B: Sendable>: Sendable {
|
|
let first: A
|
|
let second: B
|
|
|
|
init(_ first: A, _ second: B) {
|
|
self.first = first
|
|
self.second = second
|
|
}
|
|
}
|
|
|
|
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.spawn { [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)
|
|
}
|
|
}
|
|
}
|
|
|