# Concurrency Diagnostics

## Compiler messages and their fixes

| Message | What happened | Fix, in order of preference |
|---|---|---|
| `Sending 'x' risks causing data races` | A non-`Sendable` value crosses an isolation boundary | Make the type `Sendable`; take it as a `sending` parameter; restructure so it does not cross |
| `Capture of 'x' with non-sendable type 'T' in a '@Sendable' closure` | A `@Sendable` closure captures a non-`Sendable` value | Make the type `Sendable`; capture a copy of the data you need; move the state into an actor |
| `Non-sendable type 'T' returned by implicitly asynchronous call` | A non-`Sendable` result crosses isolation on the way back | Return a `Sendable` type, or do the work in the caller's isolation |
| `Main actor-isolated property 'x' can not be mutated from a nonisolated context` | Main-actor state written from nonisolated code | Mark the caller `@MainActor`; restructure; as a last step, `await MainActor.run { }` inside an async caller that must stay nonisolated |
| `Call to main actor-isolated instance method 'f()' in a synchronous nonisolated context` | Sync nonisolated code calls main-actor code | Mark the caller `@MainActor`; make it async and `await`; in SwiftUI start the call from `.task { }` |
| `Actor-isolated property 'x' can not be referenced from a nonisolated context` | Code outside the actor touches its state | `await` the access, or move the code into the actor |
| `Type 'T' does not conform to the 'Sendable' protocol` | Some stored properties are not `Sendable` | Use an immutable value or a snapshot; then actor isolation or a lock; `@unchecked Sendable` only when an internal lock exists and is written down |
| `Passing closure as a 'sending' parameter risks causing data races` | The closure captures values that may race | Capture only `Sendable` values, or keep the captured state inside one isolation domain |
| `Task-isolated value of type 'T' passed as a strongly transferred parameter` | A value is moved out of a task that still uses it | Pass a copy, or return it as `sending` |
| `Var 'x' is not concurrency-safe because it is nonisolated global shared mutable state` | A mutable global has no isolation | `@MainActor`; move it into an actor; make it a `let` of a `Sendable` type; `nonisolated(unsafe)` only with a written justification |

Exact wording varies between compiler releases; match on the key phrase.

## Effects of the 6.2 approachable settings

- `NonisolatedNonsendingByDefault` (SE-0461): async functions without explicit
  isolation run as `nonisolated(nonsending)`, on the caller's actor.
- `@concurrent` marks the functions that should leave the caller on purpose.
- `InferSendableFromCaptures` (SE-0418): closures and key paths become
  `Sendable` when everything they capture is. It is already on in Swift 6
  language mode; in Swift 5 mode it is one of the upcoming features the
  Approachable Concurrency setting enables. Either way it removes explicit
  `@Sendable` annotations.

## Adopting strict checking

- Swift 6 language mode, including 6.3, always runs complete checking and
  reports data-race problems as errors.
- The staged levels exist only before the switch: a Swift 5 target can go
  through `Minimal` or `Targeted` on the way to `Complete`, then move to the
  Swift 6 language mode.
- Fix from the leaves upward: models and DTOs first, then services, then the
  UI layer. Leaf fixes often make the upper errors vanish.
- A third-party module without `Sendable` annotations can be imported with
  `@preconcurrency import` for now. Write down when and how it will be removed.

### Order of fixes for a Sendable error

1. An immutable `struct` or `enum`.
2. A `final class` whose stored properties are all `let`.
3. Isolation to an actor or a global actor.
4. A small wrapper that synchronizes access with a lock.
5. `@unchecked Sendable`, only when a lock invariant exists that the compiler
   cannot see, and with that invariant written next to the declaration.

## Races the compiler cannot see: Thread Sanitizer

Thread Sanitizer (`-sanitize=thread`) detects data races at run time, including
ones inside code that static checking cannot reason about. In Xcode, open the
scheme editor, select the Run action, and tick Thread Sanitizer on the
Diagnostics tab.

It typically reports:

- a `Dictionary` or `Array` read and written from several threads with no
  actor or lock around it;
- delegate methods called on a queue the code did not expect;
- completion handlers writing a property while synchronous code reads it.
