Files
2026-07-13 12:22:33 +08:00

129 lines
4.9 KiB
Swift

import Foundation
extension AntigravityStatusProbe {
private static let processProbeLog = CodexBarLog.logger(LogCategories.antigravity)
private enum ProcessSnapshotFetchFailure {
case antigravity(AntigravityStatusProbeError)
case url(URLError)
case cancellation
case other(String)
init(_ error: Error) {
if let error = error as? AntigravityStatusProbeError {
self = .antigravity(error)
} else if let error = error as? URLError, error.code == .cancelled {
self = .cancellation
} else if let error = error as? URLError {
self = .url(error)
} else if error is CancellationError {
self = .cancellation
} else {
self = .other(error.localizedDescription)
}
}
var error: Error {
switch self {
case let .antigravity(error):
error
case let .url(error):
error
case .cancellation:
CancellationError()
case let .other(message):
AntigravityStatusProbeError.apiError(message)
}
}
}
private enum ProcessSnapshotFetchOutcome {
case success(index: Int, snapshot: AntigravityStatusSnapshot)
case failure(index: Int, pid: Int, failure: ProcessSnapshotFetchFailure)
}
static func fetchProcessSnapshots(
processInfos: [ProcessInfoResult],
fetch: @escaping @Sendable (ProcessInfoResult) async throws -> AntigravityStatusSnapshot)
async throws -> (snapshots: [AntigravityStatusSnapshot], lastError: Error?)
{
let outcomes = await withTaskGroup(of: ProcessSnapshotFetchOutcome.self) { group in
for (index, processInfo) in processInfos.enumerated() {
group.addTask {
do {
return try await .success(index: index, snapshot: fetch(processInfo))
} catch {
return .failure(index: index, pid: processInfo.pid, failure: .init(error))
}
}
}
var ordered = [ProcessSnapshotFetchOutcome?](repeating: nil, count: processInfos.count)
for await outcome in group {
switch outcome {
case let .success(index, _), let .failure(index, _, _):
ordered[index] = outcome
}
}
return ordered.compactMap(\.self)
}
var snapshots: [AntigravityStatusSnapshot] = []
var lastError: Error?
for outcome in outcomes {
switch outcome {
case let .success(_, snapshot):
snapshots.append(snapshot)
case let .failure(_, pid, failure):
if case .cancellation = failure {
throw CancellationError()
}
let error = failure.error
lastError = error
Self.processProbeLog.debug("Antigravity local process probe failed", metadata: [
"pid": "\(pid)",
"error": error.localizedDescription,
])
}
}
return (snapshots, lastError)
}
static func fetch(
processInfo: ProcessInfoResult,
timeout: TimeInterval,
deadline: Date) async throws -> AntigravityStatusSnapshot
{
guard let portTimeout = timeoutForNextAttempt(timeout: timeout, deadline: deadline) else {
throw AntigravityStatusProbeError.timedOut
}
let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: portTimeout)
let endpoint = try await Self.resolveWorkingEndpoint(
candidateEndpoints: Self.connectionCandidates(
listeningPorts: ports,
languageServerCSRFToken: processInfo.csrfToken,
extensionServerPort: processInfo.extensionPort,
extensionServerCSRFToken: processInfo.extensionServerCSRFToken),
timeout: timeout,
deadline: deadline)
let context = RequestContext(
endpoints: Self.requestEndpoints(
resolvedEndpoint: endpoint,
listeningPorts: ports,
languageServerCSRFToken: processInfo.csrfToken,
extensionServerPort: processInfo.extensionPort,
extensionServerCSRFToken: processInfo.extensionServerCSRFToken),
timeout: timeout,
deadline: deadline)
return try await Self.fetchSnapshot(context: context)
}
static func timeoutForNextAttempt(timeout: TimeInterval, deadline: Date?) -> TimeInterval? {
guard let deadline else { return timeout }
let remaining = deadline.timeIntervalSinceNow
guard remaining > 0 else { return nil }
return min(timeout, remaining)
}
}