apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

RadrootsBackgroundTaskQuery.swift (3154B)


      1 import Foundation
      2 
      3 /// A failed local OS inventory is unknown, never an empty successful inventory.
      4 enum RadrootsBackgroundTaskQuery {
      5     static let maximumTasks = 4096
      6     static func read<Value: Sendable>(
      7         timeoutNanoseconds: UInt64 = 5_000_000_000,
      8         _ query: (@escaping @Sendable (Value) -> Void) -> Void
      9     ) async throws -> Value {
     10         guard timeoutNanoseconds > 0, timeoutNanoseconds <= 5_000_000_000 else {
     11             throw RadrootsBackgroundTransferError.invalidRequest
     12         }
     13         let state = RadrootsBackgroundTaskQueryState<Value>()
     14         let timer = Task {
     15             do { try await Task.sleep(nanoseconds: timeoutNanoseconds) } catch { return }
     16             state.resolve(.failure(.transferFailure))
     17         }
     18         defer { timer.cancel() }
     19         return try await withTaskCancellationHandler {
     20             try await withCheckedThrowingContinuation { continuation in
     21                 guard state.install(continuation) else { return }
     22                 query { [weak state] value in state?.resolve(.success(value)) }
     23             }
     24         } onCancel: {
     25             state.resolve(.failure(.transferFailure))
     26         }
     27     }
     28 
     29     static func descriptors(_ descriptions: [String?]) throws -> [RadrootsBackgroundURLTaskDescriptor] {
     30         // Bound local parsing while retaining large recovered inventories. Refusal
     31         // preserves all tasks and receipts and grants no new enqueue authority.
     32         guard descriptions.count <= maximumTasks else { throw RadrootsBackgroundTransferError.transferFailure }
     33         return try descriptions.map { description in
     34             guard let description, description.utf8.count <= 256,
     35                   let descriptor = RadrootsBackgroundURLTaskDescriptor(taskDescription: description)
     36             else { throw RadrootsBackgroundTransferError.transferFailure }
     37             return descriptor
     38         }
     39     }
     40 }
     41 
     42 /// The lock protects every field. Only the first result wins; foreign callbacks
     43 /// and continuation resumption always execute after releasing the lock.
     44 private final class RadrootsBackgroundTaskQueryState<Value: Sendable>: @unchecked Sendable {
     45     private let lock = NSLock()
     46     private var continuation: CheckedContinuation<Value, any Error>?
     47     private var resolved = false
     48     private var earlyResult: Result<Value, RadrootsBackgroundTransferError>?
     49 
     50     func install(_ continuation: CheckedContinuation<Value, any Error>) -> Bool {
     51         lock.lock()
     52         if let earlyResult {
     53             self.earlyResult = nil
     54             lock.unlock()
     55             continuation.resume(with: earlyResult.mapError { $0 as any Error })
     56             return false
     57         }
     58         self.continuation = continuation
     59         lock.unlock()
     60         return true
     61     }
     62 
     63     func resolve(_ result: Result<Value, RadrootsBackgroundTransferError>) {
     64         lock.lock()
     65         guard !resolved else { lock.unlock(); return }
     66         resolved = true
     67         let pending = continuation
     68         continuation = nil
     69         if pending == nil {
     70             earlyResult = result
     71         }
     72         lock.unlock()
     73         pending?.resume(with: result.mapError { $0 as any Error })
     74     }
     75 }