Address PR review feedback

This commit is contained in:
austinpower1258 2026-03-05 23:09:45 -08:00
parent 1291776597
commit 0285cd1f51
3 changed files with 51 additions and 12 deletions

View file

@ -2590,6 +2590,11 @@ struct ContentView: View {
case completed(reason: String) case completed(reason: String)
} }
private enum BackgroundWorkspacePrimePolicy {
static let timeoutSeconds: TimeInterval = 2.0
static let pollIntervalNanoseconds: UInt64 = 50_000_000
}
private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async { private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async {
let shouldPrime = await MainActor.run { let shouldPrime = await MainActor.run {
tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId) tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId)
@ -2601,7 +2606,7 @@ struct ContentView: View {
dlog("workspace.backgroundPrime.start workspace=\(workspaceId.uuidString.prefix(5))") dlog("workspace.backgroundPrime.start workspace=\(workspaceId.uuidString.prefix(5))")
#endif #endif
let timeout = Date().addingTimeInterval(2.0) let timeout = Date().addingTimeInterval(BackgroundWorkspacePrimePolicy.timeoutSeconds)
while !Task.isCancelled { while !Task.isCancelled {
let state = await MainActor.run { let state = await MainActor.run {
stepBackgroundWorkspacePrime(workspaceId: workspaceId) stepBackgroundWorkspacePrime(workspaceId: workspaceId)
@ -2609,7 +2614,7 @@ struct ContentView: View {
switch state { switch state {
case .pending: case .pending:
if Date() < timeout { if Date() < timeout {
try? await Task.sleep(nanoseconds: 50_000_000) try? await Task.sleep(nanoseconds: BackgroundWorkspacePrimePolicy.pollIntervalNanoseconds)
continue continue
} }
await MainActor.run { await MainActor.run {

View file

@ -635,6 +635,7 @@ class TabManager: ObservableObject {
qos: .utility qos: .utility
) )
private var initialWorkspaceGitProbeGenerationByWorkspace: [UUID: UUID] = [:] private var initialWorkspaceGitProbeGenerationByWorkspace: [UUID: UUID] = [:]
private var initialWorkspaceGitProbeTimersByWorkspace: [UUID: [DispatchSourceTimer]] = [:]
// Recent tab history for back/forward navigation (like browser history) // Recent tab history for back/forward navigation (like browser history)
private var tabHistory: [UUID] = [] private var tabHistory: [UUID] = []
@ -870,6 +871,7 @@ class TabManager: ObservableObject {
) { ) {
let normalizedDirectory = normalizeDirectory(directory) let normalizedDirectory = normalizeDirectory(directory)
let generation = UUID() let generation = UUID()
cancelInitialWorkspaceGitProbeTimers(workspaceId: workspaceId)
initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] = generation initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] = generation
#if DEBUG #if DEBUG
@ -880,9 +882,12 @@ class TabManager: ObservableObject {
#endif #endif
let delays = Self.initialWorkspaceGitProbeDelays let delays = Self.initialWorkspaceGitProbeDelays
var timers: [DispatchSourceTimer] = []
for (index, delay) in delays.enumerated() { for (index, delay) in delays.enumerated() {
let isLastAttempt = index == delays.count - 1 let isLastAttempt = index == delays.count - 1
initialWorkspaceGitProbeQueue.asyncAfter(deadline: .now() + delay) { let timer = DispatchSource.makeTimerSource(queue: initialWorkspaceGitProbeQueue)
timer.schedule(deadline: .now() + delay, repeating: .never)
timer.setEventHandler { [weak self] in
let snapshot = Self.initialWorkspaceGitMetadataSnapshot(for: normalizedDirectory) let snapshot = Self.initialWorkspaceGitMetadataSnapshot(for: normalizedDirectory)
Task { @MainActor [weak self] in Task { @MainActor [weak self] in
self?.applyInitialWorkspaceGitMetadataSnapshot( self?.applyInitialWorkspaceGitMetadataSnapshot(
@ -895,7 +900,25 @@ class TabManager: ObservableObject {
) )
} }
} }
timers.append(timer)
timer.resume()
} }
initialWorkspaceGitProbeTimersByWorkspace[workspaceId] = timers
}
private func cancelInitialWorkspaceGitProbeTimers(workspaceId: UUID) {
guard let timers = initialWorkspaceGitProbeTimersByWorkspace.removeValue(forKey: workspaceId) else {
return
}
for timer in timers {
timer.setEventHandler {}
timer.cancel()
}
}
private func clearInitialWorkspaceGitProbe(workspaceId: UUID) {
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId)
cancelInitialWorkspaceGitProbeTimers(workspaceId: workspaceId)
} }
private func applyInitialWorkspaceGitMetadataSnapshot( private func applyInitialWorkspaceGitMetadataSnapshot(
@ -909,17 +932,17 @@ class TabManager: ObservableObject {
defer { defer {
if isLastAttempt, if isLastAttempt,
initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation { initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation {
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) clearInitialWorkspaceGitProbe(workspaceId: workspaceId)
} }
} }
guard initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation else { return } guard initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation else { return }
guard let workspace = tabs.first(where: { $0.id == workspaceId }) else { guard let workspace = tabs.first(where: { $0.id == workspaceId }) else {
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) clearInitialWorkspaceGitProbe(workspaceId: workspaceId)
return return
} }
guard workspace.panels[panelId] != nil else { guard workspace.panels[panelId] != nil else {
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) clearInitialWorkspaceGitProbe(workspaceId: workspaceId)
return return
} }
@ -927,7 +950,7 @@ class TabManager: ObservableObject {
workspace.panelDirectories[panelId] ?? workspace.currentDirectory workspace.panelDirectories[panelId] ?? workspace.currentDirectory
) )
if let currentDirectory, currentDirectory != expectedDirectory { if let currentDirectory, currentDirectory != expectedDirectory {
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) clearInitialWorkspaceGitProbe(workspaceId: workspaceId)
#if DEBUG #if DEBUG
dlog( dlog(
"workspace.gitProbe.skip workspace=\(workspaceId.uuidString.prefix(5)) " + "workspace.gitProbe.skip workspace=\(workspaceId.uuidString.prefix(5)) " +
@ -980,7 +1003,7 @@ class TabManager: ObservableObject {
process.executableURL = URL(fileURLWithPath: "/usr/bin/env") process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = ["git", "-C", directory] + arguments process.arguments = ["git", "-C", directory] + arguments
process.standardOutput = stdout process.standardOutput = stdout
process.standardError = Pipe() process.standardError = FileHandle.nullDevice
do { do {
try process.run() try process.run()
@ -988,12 +1011,13 @@ class TabManager: ObservableObject {
return nil return nil
} }
// Drain stdout while the subprocess is active so large repos cannot fill the pipe buffer.
let data = stdout.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit() process.waitUntilExit()
guard process.terminationStatus == 0 else { guard process.terminationStatus == 0 else {
return nil return nil
} }
let data = stdout.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) return String(data: data, encoding: .utf8)
} }
@ -1196,7 +1220,7 @@ class TabManager: ObservableObject {
guard tabs.count > 1 else { return } guard tabs.count > 1 else { return }
guard let index = tabs.firstIndex(where: { $0.id == workspace.id }) else { return } guard let index = tabs.firstIndex(where: { $0.id == workspace.id }) else { return }
sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1]) sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1])
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspace.id) clearInitialWorkspaceGitProbe(workspaceId: workspace.id)
AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id) AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id)
unwireClosedBrowserTracking(for: workspace) unwireClosedBrowserTracking(for: workspace)
@ -1218,7 +1242,7 @@ class TabManager: ObservableObject {
@discardableResult @discardableResult
func detachWorkspace(tabId: UUID) -> Workspace? { func detachWorkspace(tabId: UUID) -> Workspace? {
guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return nil } guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return nil }
initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: tabId) clearInitialWorkspaceGitProbe(workspaceId: tabId)
let removed = tabs.remove(at: index) let removed = tabs.remove(at: index)
unwireClosedBrowserTracking(for: removed) unwireClosedBrowserTracking(for: removed)

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import glob import glob
import os import os
import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
@ -16,7 +17,16 @@ sys.path.insert(0, str(Path(__file__).parent))
from cmux import cmux, cmuxError from cmux import cmux, cmuxError
SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") def _resolve_socket_path() -> str:
socket_path = os.environ.get("CMUX_SOCKET", "").strip()
if not socket_path:
raise cmuxError("CMUX_SOCKET is required (expected /tmp/cmux-debug-<tag>.sock)")
if not re.fullmatch(r"/tmp/cmux-debug-[^/]+\.sock", socket_path):
raise cmuxError(f"CMUX_SOCKET must be a tagged debug socket, got: {socket_path!r}")
return socket_path
SOCKET_PATH = _resolve_socket_path()
def _must(cond: bool, msg: str) -> None: def _must(cond: bool, msg: str) -> None: