Section 2/161 menit

2. Masalah yang Dipecahkan

2. Masalah yang Dipecahkan

Context Tidak Tersedia di Seluruh Call Chain

Seringkali ada data kontekstual (request ID, trace ID, user session) yang harus bisa diakses dari mana saja di call chain tanpa harus di-pass eksplisit ke setiap fungsi.

swift
// ❌ TANPA TaskLocal: pass context eksplisit di setiap level — sangat verbose
func handleRequest(requestID: String) async {
    await fetchUser(requestID: requestID)
}

func fetchUser(requestID: String) async {
    let user = await database.query(requestID: requestID)
    await processUser(user, requestID: requestID)
}

func processUser(_ user: User, requestID: String) async {
    await auditLog("Processing user", requestID: requestID)
    // requestID harus di-pass ke SETIAP fungsi di seluruh call chain
}
swift
// ✓ DENGAN TaskLocal: context otomatis tersedia di seluruh call chain
enum RequestContext {
    @TaskLocal static var requestID: String = "unknown"
    @TaskLocal static var userID: String? = nil
}

func handleRequest(requestID: String) async {
    await RequestContext.$requestID.withValue(requestID) {
        await fetchUser()
    }
}

func fetchUser() async {
    // Akses langsung tanpa parameter
    let id = RequestContext.requestID
    let user = await database.query(id: id)
    await processUser(user)
}

func processUser(_ user: User) async {
    // Tetap bisa akses tanpa parameter
    await auditLog("Processing \(RequestContext.requestID)")
}

Tidak Bisa Mengintegrasikan Concurrency dengan Event Loop Eksternal

Framework seperti SwiftNIO, SDL, atau game engine mempunyai event loop sendiri. Tanpa Custom Executor, tidak ada cara untuk membuat actor berjalan di event loop tersebut.

swift
// ❌ TANPA Custom Executor: actor berjalan di cooperative thread pool Swift
// → tidak bisa diintegrasikan dengan event loop eksternal
actor NetworkActor {
    func handleConnection() async {
        // Berjalan di Swift's cooperative pool — bukan di SwiftNIO's event loop
        // Threading mismatch menyebabkan data race atau deadlock
    }
}

// ✓ DENGAN Custom Executor: actor berjalan tepat di event loop yang diinginkan
actor NetworkActor {
    nonisolated var unownedExecutor: UnownedSerialExecutor {
        return nioEventLoop.executor.asUnownedSerialExecutor()
    }
    // Sekarang semua akses ke actor ini dijamin berjalan di NIO event loop
}

Actor Reentrancy yang Menyebabkan Invariant Violation

Actor reentrancy adalah fitur, bukan bug — tapi jika tidak dipahami, bisa menyebabkan state yang tidak konsisten.

swift
// ❌ TANPA pemahaman reentrancy: invariant bisa violated
actor Cache {
    private var data: [String: Data] = [:]
    private var pendingFetches: Set<String> = []

    func getValue(for key: String) async throws -> Data {
        if let cached = data[key] { return cached }

        // BUG: dua caller bisa sama-sama melewati check ini sebelum salah satu selesai!
        // Antara 'await' di bawah, actor bisa menerima call lain
        let fetched = try await networkFetch(key: key)
        data[key] = fetched  // bisa di-set dua kali
        return fetched
    }
}

// ✓ DENGAN pemahaman reentrancy: guard dengan pendingFetches
actor Cache {
    private var data: [String: Data] = [:]
    private var inFlight: [String: Task<Data, Error>] = [:]

    func getValue(for key: String) async throws -> Data {
        if let cached = data[key] { return cached }

        // Jika sudah ada request in-flight untuk key ini, tunggu task yang sama
        if let existing = inFlight[key] {
            return try await existing.value
        }

        let task = Task { [weak self] in
            guard let self else { throw CacheError.deallocated }
            let result = try await networkFetch(key: key)
            await self.store(result, for: key)
            return result
        }

        inFlight[key] = task
        defer { Task { await self.removeInFlight(for: key) } }
        return try await task.value
    }

    private func store(_ data: Data, for key: String) { self.data[key] = data }
    private func removeInFlight(for key: String) { inFlight.removeValue(forKey: key) }
}

enum CacheError: Error { case deallocated }