Files
markbaseengine/Sources/MarkBaseServer/CrossDeviceClient.swift
T
MarkBase Admin 8a66b9086a
CI / build (push) Has been cancelled
CI / unit-tests (push) Has been cancelled
CI / lint (push) Has been cancelled
v2: Initial clean branch with unit tests + CI/CD pipeline
- Started from ac75faa (initial E4B-MarkBase integration)
- Kept Sources/ (all engine code) + Package.swift + .gitignore
- Removed all ad-hoc tests, documentation, scripts, Python files
- Added Tests/00_Unit/ (MathTest, TokenizerTest, SamplerTest)
- Added .gitea/workflows/ci.yaml (build + unit tests + lint)
- Added Scripts/check_resources.sh (memory-aware test runner)
- Added Tests/Manifest.json (resource requirements for all tests)
- Focus: 4-bit quantized models only
2026-07-05 13:29:25 +08:00

140 lines
4.5 KiB
Swift

import Foundation
// ─────────────────────────────────────────────────────────────
// Cross-Device Client
// ─────────────────────────────────────────────────────────────
/// Cross-device client for making requests to other nodes
public final class CrossDeviceClient: @unchecked Sendable {
private let session: URLSession
private let loadBalancer: LoadBalancer
private let timeout: TimeInterval
public init(
loadBalancer: LoadBalancer,
timeout: TimeInterval = 30
) {
self.loadBalancer = loadBalancer
self.timeout = timeout
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = timeout
self.session = URLSession(configuration: config)
}
/// Send request to cluster
public func sendToCluster(
endpoint: String,
method: String = "POST",
body: Data? = nil
) async throws -> CrossDeviceResponse {
guard let node = loadBalancer.getNextNode() else {
throw CrossDeviceError.noHealthyNodes
}
return try await sendToNode(
node: node,
endpoint: endpoint,
method: method,
body: body
)
}
/// Send request to specific node
public func sendToNode(
node: DeviceNode,
endpoint: String,
method: String = "POST",
body: Data? = nil
) async throws -> CrossDeviceResponse {
let url = URL(string: "\(node.baseURL)\(endpoint)")!
var request = URLRequest(url: url)
request.httpMethod = method
request.httpBody = body
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = timeout
let startTime = Date()
do {
let (data, response) = try await session.data(for: request)
let latency = Date().timeIntervalSince(startTime)
guard let httpResponse = response as? HTTPURLResponse else {
throw CrossDeviceError.invalidResponse
}
// Update node status
loadBalancer.updateNodeStatus(
id: node.id,
status: httpResponse.statusCode < 500 ? .healthy : .degraded,
load: nil
)
return CrossDeviceResponse(
requestId: UUID().uuidString,
statusCode: httpResponse.statusCode,
body: data,
latency: latency,
nodeId: node.id
)
} catch {
let latency = Date().timeIntervalSince(startTime)
// Update node status
loadBalancer.updateNodeStatus(id: node.id, status: .unhealthy)
throw CrossDeviceError.requestFailed(error)
}
}
/// Broadcast request to all healthy nodes
public func broadcastToAll(
endpoint: String,
method: String = "POST",
body: Data? = nil
) async throws -> [CrossDeviceResponse] {
let nodes = loadBalancer.getNodes().filter { $0.status == .healthy }
return try await withThrowingTaskGroup(of: CrossDeviceResponse.self) { group in
for node in nodes {
group.addTask {
try await self.sendToNode(
node: node,
endpoint: endpoint,
method: method,
body: body
)
}
}
var responses: [CrossDeviceResponse] = []
for try await response in group {
responses.append(response)
}
return responses
}
}
}
/// Cross-device errors
public enum CrossDeviceError: Error, LocalizedError {
case noHealthyNodes
case invalidResponse
case requestFailed(Error)
case timeout
public var errorDescription: String? {
switch self {
case .noHealthyNodes:
return "No healthy nodes available"
case .invalidResponse:
return "Invalid response from node"
case .requestFailed(let error):
return "Request failed: \(error.localizedDescription)"
case .timeout:
return "Request timed out"
}
}
}