SE-0412: String Concurrency for global variables
Introduction
データレースを発生させないGlobalVariablesの使用を実現する方法を提案する。
ここでは、 GlobalVariablesには、静的なStorgateも含まれる。(Global Scopeで宣言されたletや stored varや、static member variable)
Motivation
Globalな状態は、どこからでもそのメモリにアクセスできる性質の影響で、Concurrencyプログラムにおいて、扱うのが難しい。
特に、GlobalVariablesは、ConcurrencyのData Isolation Checkingにおいて、isolationを強制する仕組みが適用できないため、Concurrencyで扱うことが難しい。
LocalでキャプチャされていないVariableは、local contextからしかアクセスされない、それは、非明示的にそれらをisolatedにする。
値型の StoredPropertyは、排他的なルールによって、すでに、isolatedになっている。
参照型のStoredPropertyは、Sendableに準拠させることや、Actor制約を課すことで、isolatedにすることができる。
しかし、GlobalVariablesはどこからでもアクセスできる。そのため、前述したような他のケースへの対処方法が適用できない。
var value = 1
func f() {
value = 2 // warning: reference to var 'value' is not concurrency-safe because it involves shared mutable state
}
Proposed solution
StrictConcurrency Checkingを有効にしている場合は、すべてのGlobalVariablesは isolatedであるか、GlobalActorになっているか、その両方である必要がある。
ImmutableでSendableなGlobalVariablesはどんなcontextからアクセスされても安全である。
そうでないなら、isolationが必要になる。
Top-Level GlobalVariablesはすでに、MainActorに隔離されている。それゆえ、この提案のsolutionを最初から満している。
Detailed design
These requirements can be enforced in the type checker at declaration time.
Aこの提案は、変数の宣言時の型チェックにルールを課す。
GlobalVariablesがlazyで初期化される場合であっても、その初期化がthread-safeであることを保証される。そのため、StrictConcurrencyチェックに対応するために、それ以上のことをしなくてよい。
Deveploperにデータの隔離の管理を任せることで、このstaticCheckを無効にしたい場合があるかもしれない。(e.g. associated global lock を仕様したserializing data accessなど)
nonisolated(unsafe) アトリビュートはGlobalVariablesのアノテーションに仕様できる。
これは、GlobalVariablesへの静的なData isolationのチェックを無効にするが、Data isolationを達成するためにはdataへのアクセスの同期的処理の正しい実装が必要となる。排他性の強制や Thread Sanitizer などのツールによる動的なランタイム分析でも障害を特定できる可能性がある。
nonisolated(unsafe) var global: String
LocalVariableへの同じアノテーションもLocalVariableが非同期的にアクセスされる場合の静的なDiagnosticが生成されるのを防ぐことができる。
func f() async {
nonisolated(unsafe) var value = 1
let task = Task {
value = 2
return value
}
print(await task.value)
}
nonisolatedは文脈的なキーワードなので、 script modeでトップレベルの変数を宣言する直前のnonisolatedを独立した行で使用したときに、引数のunsafeを指定したnonisolatedとう関数を呼び出しとも解釈される可能性があり、曖昧性が生じる。
@preconcurrency importを使用してimportしたモジュールは、importしたGlobalVariablesが明示的にConcurrencyのアノテーションをしてないことへのData siolation Checkingによって生じるいかなるエラーも抑制する。
@preconcurrency importされた Concurrency unsafeなGlobalVariablesの使用は使用している箇所でwarningになる。
注意として、他の言語からimportされるものは、非明示てきにpreconcurrency importになる。
他の言語からimportされたGlobalVariablesの安全性を強制するツールがいくつか存在する。
例えば、C or Obj-cでの__attribute__((swift_attr("@MainActor")))などを使用したGlobalActorへの隔離や、適切なisolationまたはロックを適切に宣言する、より安全な API 内でアクセスをラップします。
Source compatibility
新しい制約の影響で、この変更は Strict Concurrency Checkingが有効になっている時に、いくつかの型宣言で変更を要求する。
そのようなソースコードへの変更は、Concurrencyの機能がある どの Swift versionへの後方互換性がある。
nonisolated(unsafe)のtop-levelでの変数宣言での曖昧性解決は nonisolatedという関数名の関数が存在し、その第一引数がunsafeである時に それを関数名ではなく nonisolated(unsafe) annotatoinとして解釈してしまうため、問題が起こる。
すごくレアなケースそう。
ABI compatibility
ABI compatibility: OK
型宣言の変更がプロジェクトの採用に影響を与えると、そのプロジェクトの ABI に影響を与える可能性があります。
Implications on adoption
Strict Concurrency Checkを採用しているプロジェクトでは、一部のグローバル変数タイプを変更する必要がある場合があります。
有効にしてみると、意外と影響があるのでは。