-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
Description
The following program compiles and runs using the Xcode Swift 6.2 compiler in Swift 6 mode with no warnings or errors:
import Foundation
class Stateful {
var x: Int
init() { x = 0 }
}
class Entry {
let id: Int
var s: Stateful
init(_ id: Int, s: Stateful) {
self.id = id
self.s = s
}
func asyncWork() async {
s.x += 1
print("Task \(id): x=\(s.x)")
for _ in 1...500 { }
print("Task \(id): x=\(s.x)")
}
}
@MainActor class MyClass {
var entries: [Entry] = []
func tenTasks() async {
let s = Stateful()
entries = (0...9).map { Entry($0, s: s) }
let totalCount = entries.count
await withTaskGroup(of: Void.self) { group in
var i = 0
while i < totalCount {
let entry = entries[i]
group.addTask { await entry.asyncWork() }
i += 1
}
for await _ in group { }
}
}
}
print("Begin")
await MyClass().tenTasks()
print("End")
Yet it has an obvious race condition on x, with its value changing from the point of view of a task without the task yielding or modifying it. Here’s one output:
Begin
Task 0: x=1
Task 1: x=2
Task 2: x=3
Task 3: x=4
Task 0: x=4
Task 5: x=5
Task 4: x=6
Task 6: x=7
Task 7: x=8
Task 1: x=8
Task 9: x=9
Task 8: x=10
Task 2: x=10
Task 5: x=10
Task 3: x=10
Task 6: x=10
Task 4: x=10
Task 7: x=10
Task 9: x=10
Task 8: x=10
End
Were I to change the line
entries = (0...9).map { Entry($0, s: s) }
to
let entries = (0...9).map { Entry($0, s: s) }
then the compiler would correctly diagnose the problem:
Passing closure as a 'sending' parameter risks causing data races between main actor-isolated code and concurrent execution of the closure
but the program as originally listed races without generating any errors or warnings. Is this intentional?
I’m running Swift 6.2 on the current release Xcode 26 with language set to Swift 6 and “nonisolated(nonsending) By Default” set to YES, although the bug also appears in older versions of Swift.
Reproduction
import Foundation
class Stateful {
var x: Int
init() { x = 0 }
}
class Entry {
let id: Int
var s: Stateful
init(_ id: Int, s: Stateful) {
self.id = id
self.s = s
}
func asyncWork() async {
s.x += 1
print("Task \(id): x=\(s.x)")
for _ in 1...500 { }
print("Task \(id): x=\(s.x)")
}
}
@MainActor class MyClass {
var entries: [Entry] = []
func tenTasks() async {
let s = Stateful()
entries = (0...9).map { Entry($0, s: s) }
let totalCount = entries.count
await withTaskGroup(of: Void.self) { group in
var i = 0
while i < totalCount {
let entry = entries[i]
group.addTask { await entry.asyncWork() }
i += 1
}
for await _ in group { }
}
}
}
print("Begin")
await MyClass().tenTasks()
print("End")
Expected behavior
Compiler in Swift 6 mode rejects the code due to the race condition
Environment
Swift 6.2 in Xcode 26 release, but also happens in earlier Swift versions.
Additional information
See discussion on Swift forum.