129 lines
4.9 KiB
Swift
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)
|
|
}
|
|
}
|