Files
steipete--codexbar/Sources/CodexBarCore/CostUsageScanExecutor.swift
T
2026-07-13 12:22:33 +08:00

131 lines
4.9 KiB
Swift

import Foundation
/// Cost-usage scans read and parse the full local session corpus synchronously and can run for
/// minutes on large archives. Executing that work inline on Swift's cooperative thread pool
/// starves every other async task in the process — menus freeze while the main thread sits idle —
/// and overlapping provider scans multiply both the pool pressure and the disk load. This
/// executor pins all corpus scans to a single serial utility queue off the cooperative pool, so
/// long scans cost one dedicated thread instead of the app's async runtime.
public enum CostUsageScanExecutor {
public static let queueLabel = "com.steipete.codexbar.cost-usage-scan"
private static let queue = DispatchQueue(label: queueLabel, qos: .utility)
private final class RunState<Value: Sendable>: @unchecked Sendable {
private enum Phase {
case initial
case queued
case running
case completed
}
private let lock = NSLock()
private var phase: Phase = .initial
private var cancellationRequested = false
private var continuation: CheckedContinuation<Value, Error>?
func install(_ continuation: CheckedContinuation<Value, Error>) -> Bool {
let shouldEnqueue: Bool
let shouldResumeCancellation: Bool
self.lock.lock()
if self.cancellationRequested {
self.phase = .completed
shouldEnqueue = false
shouldResumeCancellation = true
} else {
self.phase = .queued
self.continuation = continuation
shouldEnqueue = true
shouldResumeCancellation = false
}
self.lock.unlock()
if shouldResumeCancellation {
continuation.resume(throwing: CancellationError())
}
return shouldEnqueue
}
func begin() -> Bool {
self.lock.lock()
defer { self.lock.unlock() }
guard self.phase == .queued else { return false }
self.phase = .running
return true
}
func cancel() {
let continuation: CheckedContinuation<Value, Error>?
self.lock.lock()
self.cancellationRequested = true
if self.phase == .queued {
self.phase = .completed
continuation = self.continuation
self.continuation = nil
} else {
continuation = nil
}
self.lock.unlock()
continuation?.resume(throwing: CancellationError())
}
func checkCancellation() throws {
self.lock.lock()
let cancellationRequested = self.cancellationRequested
self.lock.unlock()
if cancellationRequested {
throw CancellationError()
}
}
func complete(with result: Result<Value, Error>) {
let continuation: CheckedContinuation<Value, Error>?
let resolvedResult: Result<Value, Error>
self.lock.lock()
guard self.phase == .running else {
self.lock.unlock()
return
}
self.phase = .completed
continuation = self.continuation
self.continuation = nil
resolvedResult = self.cancellationRequested ? .failure(CancellationError()) : result
self.lock.unlock()
continuation?.resume(with: resolvedResult)
}
}
/// Runs `work` on the serial scan queue and bridges Swift task cancellation into the
/// scanner's cooperative `checkCancellation` callbacks. Work that is still queued when the
/// awaiting task is cancelled resumes immediately with `CancellationError` instead of
/// waiting behind an in-flight scan.
public static func run<T: Sendable>(
_ work: @escaping @Sendable (_ checkCancellation: @escaping @Sendable () throws -> Void) throws -> T)
async throws -> T
{
try await self.run(on: self.queue, work)
}
static func run<T: Sendable>(
on queue: DispatchQueue,
_ work: @escaping @Sendable (_ checkCancellation: @escaping @Sendable () throws -> Void) throws -> T)
async throws -> T
{
let state = RunState<T>()
let checkCancellation: @Sendable () throws -> Void = {
try state.checkCancellation()
}
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
guard state.install(continuation) else { return }
queue.async {
guard state.begin() else { return }
state.complete(with: Result { try work(checkCancellation) })
}
}
} onCancel: {
state.cancel()
}
}
}