Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Packages/macOS/CmuxNext/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,11 @@ let packageTargets: [Target] = uiLayerPreviewTargets + [
],
swiftSettings: daemonSwiftSettings
),
.testTarget(
name: "CmuxNextCloudTests",
dependencies: ["CmuxNextCloud"],
swiftSettings: daemonSwiftSettings
),
.target(
name: "CmuxNextCloudUI",
dependencies: ["CmuxNextCloud", "CmuxNextDesign"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ struct CloudStartChat {
/// this Mac's tab: the chat is the machine's now.
private func open(_ start: AgentPaneCloudStart, tab: TabModel, on machine: CloudMachineSession, cwd: String?,
tabKey: String) async throws -> String {
await CloudOutdatedMachine.updateBeforeUsingCapability(
holds: { services.agentTabs.holdsTabs(machine.daemon) },
link: { CloudMachineUpdates.handshake(machine, machines: services.machines) },
update: { await CloudMachineUpdates.updateNow(machine.machineID, services: services) })
let context = AppActionContext(services: services)
let anchor = try await CloudHandlers.terminalAnchor(on: machine, context)
let pending = try services.agentTabs.open(in: anchor.pane.handle, of: machine.daemon, seed: AgentPaneSeedSource(start.seed(cwd: cwd)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ public struct CloudOutdatedMachine {
!ok && errorCode == code && side != "source"
}

/// Updates a machine before using a capability that its current daemon does not advertise.
/// The update restarts the daemon, so the capability must be read from the new handshake rather
/// than from the old connection. If the update cannot run or the new link does not arrive before
/// `relink`, this returns and the caller's existing capability refusal remains the final result.
@MainActor
public static func updateBeforeUsingCapability(
holds: @escaping @MainActor @Sendable () -> Bool,
link: @escaping @MainActor @Sendable () -> String?,
relink: Duration = .seconds(90),
update: @escaping @MainActor @Sendable () async -> Bool
) async {
guard !holds() else { return }
let before = link()
guard await update() else { return }
_ = await CloudWait.holds(within: relink) {
link().map { $0 != before } ?? false
}
}

/// Runs `copy` on the machine's link socket (`socket`, nil when not connected: `notConnected` is thrown); when
/// its answer (read by `answer`) is the machine's older daemon refusing it (``updatesBeforeCopyingAgain(ok:errorCode:side:)``)
/// and `update` updated the machine (true), runs it once more. Otherwise the first answer stands: nothing else updates it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import CmuxNextCloud
import Testing

@Suite
@MainActor
struct CloudOutdatedMachineTests {
@Test func anOutdatedMachineUpdatesBeforeItsCapabilityIsUsed() async {
var handshake = "old"
var holdsTabs = false
var updates = 0

await CloudOutdatedMachine.updateBeforeUsingCapability(
holds: { holdsTabs },
link: { handshake },
relink: .seconds(1),
update: {
updates += 1
await Task.yield()
handshake = "new"
holdsTabs = true
return true
})

#expect(updates == 1)
#expect(handshake == "new")
#expect(holdsTabs)
}

@Test func anUpdateThatDoesNotRelinkReturnsWithinTheBoundedWait() async {
var holdsTabs = false
var updates = 0

await CloudOutdatedMachine.updateBeforeUsingCapability(
holds: { holdsTabs },
link: { "old" },
relink: .milliseconds(10),
update: {
updates += 1
return true
})

#expect(updates == 1)
#expect(!holdsTabs)
}

@Test func anAlreadyCurrentMachineDoesNotUpdate() async {
var updates = 0

await CloudOutdatedMachine.updateBeforeUsingCapability(
holds: { true },
link: { "current" },
relink: .milliseconds(10),
update: {
updates += 1
return true
})

#expect(updates == 0)
}
}
Loading