Skip to content

Commit a0e05f7

Browse files
committed
[Educational Notes] Add an explanation for `sending 'x' risks causing
data races`.
1 parent f1492fe commit a0e05f7

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

include/swift/AST/EducationalNotes.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ EDUCATIONAL_NOTES(result_builder_missing_build_array,
8383
EDUCATIONAL_NOTES(multiple_inheritance,
8484
"multiple-inheritance.md")
8585

86+
EDUCATIONAL_NOTES(regionbasedisolation_named_send_yields_race,
87+
"sending-risks-data-race.md")
88+
EDUCATIONAL_NOTES(regionbasedisolation_type_send_yields_race,
89+
"sending-risks-data-race.md")
90+
8691
EDUCATIONAL_NOTES(error_in_swift_lang_mode,
8792
"error-in-future-swift-version.md")
8893

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Sending value risks causing data races
2+
3+
If a type does not conform to `Sendable` the compiler will enforce that each instance of that type is only accessed by one concurrency domain at a time. The `sending 'x' risks causing data races` error indicates that your code can access a non-`Sendable` value from multiple concurrency domains at once.
4+
5+
For example, if a value can be accessed from the main actor, it's invalid to send the same instance to another concurrency domain while the main actor can still access it. This mistake is common when calling an `async` function on a class from the main actor:
6+
7+
```swift
8+
class Person {
9+
var name: String = ""
10+
11+
func printNameConcurrently() async {
12+
print(name)
13+
}
14+
}
15+
16+
@MainActor
17+
func onMainActor(person: Person) async {
18+
await person.printNameConcurrently()
19+
}
20+
```
21+
22+
The above code produces:
23+
24+
```
25+
await person.printNameConcurrently()
26+
|- error: sending 'person' risks causing data races
27+
`- note: sending main actor-isolated 'person' to nonisolated instance method 'printNameConcurrently()' risks causing data races between nonisolated and main actor-isolated uses
28+
```
29+
30+
This happens because the `printNameConcurrently` function runs off of the main actor, and the `onMainActor` function suspends while waiting for `printNameConcurrently` to complete. While suspended, the main actor can run other tasks that still have access to `person`, which can lead to a data race.
31+
32+
The most common fix is to change the `async` method to run on the caller's actor using the `@execution(caller)` attribute:
33+
34+
```swift
35+
class Person {
36+
var name: String = ""
37+
38+
@execution(caller)
39+
func printNameConcurrently() async {
40+
print(name)
41+
}
42+
}
43+
44+
@MainActor
45+
func onMainActor(person: Person) async {
46+
await person.printNameConcurrently()
47+
}
48+
```
49+
50+
This eliminates the risk of data-races because `printNameConcurrently` will continue to run on the main actor, so all access to `person` is serialized.
51+
52+
You can also enable the `AsyncCallerExecution` upcoming feature to make `@execution(caller)` the default for async functions on non-`Sendable` types.

0 commit comments

Comments
 (0)