Section 6/161 menit
6. Actor Isolation sebagai Safety Boundary
6. Actor Isolation sebagai Safety Boundary
Isolation Domains yang Ada
swift
// 1. Actor isolation — custom serial queue
actor BankAccount {
private var balance: Decimal
private var transactions: [Transaction] = []
init(initialBalance: Decimal) {
self.balance = initialBalance
}
// Hanya bisa diakses dengan await dari luar actor
func transfer(amount: Decimal, to destination: BankAccount) async throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
// Ini adalah suspension point — actor bisa handle request lain di sini
// (re-entrancy) — penting untuk dipahami!
try await destination.receive(amount: amount)
// Balance mungkin sudah berubah jika ada concurrent call!
// Cek ulang setelah await
guard balance >= amount else {
throw BankError.insufficientFunds // rollback scenario
}
balance -= amount
transactions.append(.debit(amount: amount))
}
func receive(amount: Decimal) {
balance += amount
transactions.append(.credit(amount: amount))
}
var currentBalance: Decimal { balance }
}
// 2. @MainActor — main thread isolation
@MainActor
class UIStateManager {
var isLoading: Bool = false
var errorMessage: String?
var items: [DisplayItem] = []
// Method ini hanya bisa dipanggil dari main thread
func showLoading() { isLoading = true }
func hideLoading() { isLoading = false }
}
// 3. Custom global actor
@globalActor
actor DatabaseActor {
static let shared = DatabaseActor()
}
@DatabaseActor
class DatabaseManager {
private var connection: DatabaseConnection?
func query(_ sql: String) async throws -> [Row] {
guard let conn = connection else { throw DBError.notConnected }
return try await conn.execute(sql)
}
}
Actor Re-Entrancy — Masalah Tersembunyi
swift
// ❌ Re-entrancy menyebabkan invariant yang rusak
actor InventoryManager {
private var stock: [ProductID: Int] = [:]
func reserveItem(productId: ProductID, quantity: Int) async throws {
guard let available = stock[productId], available >= quantity else {
throw InventoryError.outOfStock
}
// ⚠️ SUSPENSION POINT — actor bisa handle request lain di sini!
try await chargePayment(for: productId, quantity: quantity)
// Saat kembali, stock[productId] mungkin sudah berubah
// karena ada request lain yang masuk saat kita suspend
stock[productId]! -= quantity // bisa jadi negative!
}
}
// ✓ Pola aman: check-then-act dalam satu atomic operation
actor InventoryManager {
private var stock: [ProductID: Int] = [:]
private var reservations: Set<ReservationID> = []
func reserveItem(productId: ProductID, quantity: Int) async throws -> ReservationID {
// Atomic check-and-reserve sebelum suspend
guard let available = stock[productId], available >= quantity else {
throw InventoryError.outOfStock
}
// Reserve dulu (tanpa suspend)
let reservationId = ReservationID()
stock[productId]! -= quantity
reservations.insert(reservationId)
// Baru payment — jika gagal, rollback
do {
try await chargePayment(for: productId, quantity: quantity)
} catch {
// Rollback reservation
stock[productId]! += quantity
reservations.remove(reservationId)
throw error
}
return reservationId
}
}