macos: stabilize control connection wait

This commit is contained in:
Peter Steinberger
2025-12-08 21:31:33 +00:00
parent abca8535cf
commit 6298c586fd
3 changed files with 1810 additions and 1788 deletions

View File

@@ -13,6 +13,44 @@ struct ControlHeartbeatEvent: Codable {
let reason: String?
}
// Handles single-shot continuation resumption without Sendable capture issues
actor ConnectionWaiter {
private var cont: CheckedContinuation<Void, Error>?
private var resumed = false
private var pendingResult: Result<Void, Error>?
func wait() async throws {
try await withCheckedThrowingContinuation { (c: CheckedContinuation<Void, Error>) in
if let pending = pendingResult {
pendingResult = nil
resumed = true
c.resume(with: pending)
} else {
cont = c
}
}
}
func succeed() {
resume(.success(()))
}
func fail(_ error: Error) {
resume(.failure(error))
}
private func resume(_ result: Result<Void, Error>) {
if resumed { return }
if let c = cont {
resumed = true
cont = nil
c.resume(with: result)
} else {
pendingResult = result
}
}
}
struct ControlHealthSnapshot: Codable {
struct Web: Codable {
let linked: Bool
@@ -169,29 +207,36 @@ final class ControlChannel: ObservableObject {
let conn = NWConnection(host: host, port: port, using: .tcp)
self.connection = conn
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
conn.stateUpdateHandler = { [weak self, weak conn] state in
guard let self else { return }
switch state {
case .ready:
Task { @MainActor in self.state = .connected }
let waiter = ConnectionWaiter()
conn.stateUpdateHandler = { [weak self, weak conn] state in
switch state {
case .ready:
Task { @MainActor in self?.state = .connected }
Task {
await waiter.succeed()
conn?.stateUpdateHandler = nil
cont.resume(returning: ())
case let .failed(err):
Task { @MainActor in self.state = .degraded(err.localizedDescription) }
conn?.stateUpdateHandler = nil
cont.resume(throwing: err)
case let .waiting(err):
Task { @MainActor in self.state = .degraded(err.localizedDescription) }
conn?.stateUpdateHandler = nil
cont.resume(throwing: err)
default:
break
}
case let .failed(err):
Task { @MainActor in self?.state = .degraded(err.localizedDescription) }
Task {
await waiter.fail(err)
conn?.stateUpdateHandler = nil
}
case let .waiting(err):
Task { @MainActor in self?.state = .degraded(err.localizedDescription) }
Task {
await waiter.fail(err)
conn?.stateUpdateHandler = nil
}
default:
break
}
conn.start(queue: .global())
}
conn.start(queue: .global())
try await waiter.wait()
self.listenTask = Task.detached { [weak self] in
await self?.listen()
}

File diff suppressed because it is too large Load Diff

View File

@@ -552,26 +552,6 @@ private final class ClickCatcher: NSView {
}
}
private struct CloseHoverButton: View {
var onClose: () -> Void
var body: some View {
Button(action: self.onClose) {
Image(systemName: "xmark")
.font(.system(size: 12, weight: .bold))
.foregroundColor(Color.white.opacity(0.85))
.frame(width: 22, height: 22)
.background(Color.black.opacity(0.35))
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.35), radius: 6, y: 2)
}
.buttonStyle(.plain)
.focusable(false)
.contentShape(Circle())
.padding(6)
}
}
private struct CloseButtonOverlay: View {
var isVisible: Bool
var onClose: () -> Void