Section 7/161 menit
7. Actor Reentrancy dan Isolation Deep Dive
7. Actor Reentrancy dan Isolation Deep Dive
Memahami Reentrancy
Actor di Swift bersifat reentrant: ketika actor menunggu di await, ia bisa melayani request lain. Ini berbeda dari mutex yang memblokir.
swift
// Visualisasi reentrancy
actor BankAccount {
var balance: Double = 1000.0
func transfer(amount: Double, to target: BankAccount) async throws {
guard balance >= amount else { throw BankError.insufficient }
balance -= amount // (A) balance = 900
// ⚠️ Suspension point: actor bisa layani request lain di sini!
try await target.deposit(amount) // (B) bisa ada call lain ke self masuk
// Jika ada call lain yang ubah balance antara (A) dan (C)
// state bisa tidak konsistent
} // (C)
func deposit(_ amount: Double) async {
balance += amount
}
}
// Contoh race yang mungkin terjadi:
// 1. transfer(500) dimulai: balance = 500
// 2. Saat await, transfer(600) masuk: balance = 500 - 600 = -100 (!!)
// 3. transfer(500) resume: deposit selesai
// → balance negatif meski ada guard di awal
swift
// Fix: snapshot state sebelum suspension, validasi setelah resume
actor BankAccount {
var balance: Double = 1000.0
func transfer(amount: Double, to target: BankAccount) async throws {
// Validasi dan kurangi balance SEBELUM suspension
guard balance >= amount else { throw BankError.insufficient }
balance -= amount
do {
try await target.deposit(amount)
} catch {
// Rollback jika deposit gagal
balance += amount
throw error
}
// Tidak ada state yang perlu di-revalidasi setelah resume
}
func deposit(_ amount: Double) async {
balance += amount
}
}
enum BankError: Error { case insufficient }
nonisolated dan Sendable
swift
// nonisolated: method yang tidak butuh actor isolation
actor UserCache {
private var cache: [String: User] = [:]
// ✓ nonisolated: tidak akses mutable state
nonisolated var description: String {
"UserCache instance"
}
// ✓ nonisolated computed property aman jika hanya akses let
nonisolated let cacheVersion: Int = 1
// ⚠️ Tidak bisa nonisolated jika akses var
func updateUser(_ user: User) {
cache[user.id] = user
}
}
// Sendable: syarat untuk crossing actor boundary
struct User: Sendable {
let id: String
let name: String
// Semua stored properties harus Sendable
}
// Closure yang crossing actor boundary harus @Sendable
func scheduleWork(actor: some Actor, work: @escaping @Sendable () async -> Void) {
Task {
await work()
}
}
Isolated Parameters (Swift 5.7+)
swift
// isolated parameter: specify bahwa fungsi berjalan di isolasi aktor tertentu
func updateUI(isolated actor: MainActor.Type = MainActor.self) {
// Dipanggil di main actor context
label.text = "Updated"
}
// Lebih berguna dengan actor instance
func process(isolated account: BankAccount) async {
// Berjalan di isolasi account actor
// Bisa akses internal state account secara langsung
account.balance += 100 // aman karena isolated
}
// Gunakan untuk fungsi yang perlu akses ke actor tanpa jadi method
extension BankAccount {
static func merge(
_ source: isolated BankAccount,
into destination: isolated BankAccount
) async {
// Harus berjalan di kedua actor — tidak mungkin secara simultan
// Swift memanggil ini dua kali secara bergantian
let amount = source.balance
source.balance = 0
destination.balance += amount
}
}
var label: UILabel { fatalError() }