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
534 changes: 0 additions & 534 deletions Resources/Localizable.xcstrings

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions Sources/App/AggregateMemoryPressureResponder.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

/// Warns about complete aggregate metrics and schedules only safe idle-agent
/// Responds to complete aggregate metrics by scheduling only safe idle-agent
/// hibernation while that same aggregate pressure remains observable.
@MainActor
final class AggregateMemoryPressureResponder: MemoryPressureResponder {
Expand All @@ -11,16 +11,13 @@ final class AggregateMemoryPressureResponder: MemoryPressureResponder {

private let controller: AgentHibernationController
private let isAggregatePressureActive: @MainActor () -> Bool
private let onAggregatePressureWarning: @MainActor (MemoryPressureSnapshot) -> Void

init(
controller: AgentHibernationController,
isAggregatePressureActive: @escaping @MainActor () -> Bool,
onAggregatePressureWarning: @escaping @MainActor (MemoryPressureSnapshot) -> Void = { _ in }
isAggregatePressureActive: @escaping @MainActor () -> Bool
) {
self.controller = controller
self.isAggregatePressureActive = isAggregatePressureActive
self.onAggregatePressureWarning = onAggregatePressureWarning
}

func shedMemory(for snapshot: MemoryPressureSnapshot) -> MemoryPressureShedResult {
Expand All @@ -33,7 +30,6 @@ final class AggregateMemoryPressureResponder: MemoryPressureResponder {
)
}

onAggregatePressureWarning(snapshot)
let responderID = memoryPressureResponderID
let severity = aggregate.severity
let didSchedule = controller.reclaimIdleAgentsForMemoryPressure(
Expand Down
7 changes: 2 additions & 5 deletions Sources/App/MemoryPressureMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ final class MemoryPressureMonitor {
private(set) var physicalFootprintBytes: UInt64?
private(set) var aggregateMemoryPressure: MemoryPressureAggregateSnapshot?

@ObservationIgnored
var onPersistentCriticalPressure: (@MainActor (MemoryPressureSnapshot) -> Void)?

@ObservationIgnored
var onAggregatePressureCleared: (@MainActor () -> Void)?

Expand Down Expand Up @@ -267,7 +264,7 @@ final class MemoryPressureMonitor {
if let aggregateMemoryPressure,
aggregateMemoryPressure.isActionable {
// Aggregate pressure gets its own dispatch lane. This preserves
// the visible warning + idle-agent hibernation contract without
// the idle-agent hibernation contract without
// making aggregate thresholds release unrelated hidden resources.
let aggregateSnapshot = MemoryPressureSnapshot(
severity: aggregateMemoryPressure.severity,
Expand All @@ -278,7 +275,7 @@ final class MemoryPressureMonitor {
registry.dispatch(aggregateSnapshot, signal: .aggregate)
}
if evaluation.didBecomePersistentCritical {
onPersistentCriticalPressure?(evaluation.snapshot)
Self.logger.notice("memoryPressure.persistentCritical")
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/App/MemoryPressureResponderRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class MemoryPressureResponderRegistry {
category: "MemoryPressure"
)

private var respondersByID: [String: any MemoryPressureResponder] = [:]
var respondersByID: [String: any MemoryPressureResponder] = [:]

func register(_ responder: any MemoryPressureResponder) {
respondersByID[responder.memoryPressureResponderID] = responder
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/MemoryPressureStateTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct MemoryPressureStateTracker: Sendable {
)
// Aggregate pressure is a distinct signal lane. Keep the legacy
// system/footprint severity here so aggregate pressure cannot trigger
// unrelated resource shedding or the persistent-critical callback.
// unrelated resource shedding or persistent-critical diagnostics.
// The monitor dispatches the aggregate snapshot explicitly to the
// aggregate responder after this evaluation.
let nextSeverity = max(systemSeverity ?? .normal, footprintSeverity)
Expand Down
67 changes: 3 additions & 64 deletions Sources/AppDelegate+PaneMemoryGuardrail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ extension AppDelegate {
/// memory-pressure monitor. The pane guardrail keeps its existing
/// process-tree accounting timer; global pressure is handled through
/// responder registration. Aggregate pressure is intentionally isolated to
/// its warning plus idle-agent-hibernation responder.
/// its idle-agent-hibernation responder. Resource diagnostics never enter
/// the user notification pipeline.
func startPaneMemoryGuardrailIfNeeded() {
let guardrail = PaneMemoryGuardrail.shared
guardrail.paneProvider = { [weak self] in
Expand All @@ -23,7 +24,7 @@ extension AppDelegate {
}
}

private func startMemoryPressureMonitorIfNeeded() {
func startMemoryPressureMonitorIfNeeded() {
let monitor = MemoryPressureMonitor.shared
monitor.registry.register(
RendererRealizationMemoryPressureResponder(
Expand All @@ -41,9 +42,6 @@ extension AppDelegate {
isAggregatePressureActive: { [weak monitor] in
guard let aggregate = monitor?.aggregateMemoryPressure else { return false }
return aggregate.isActionable && aggregate.severity >= .warning
},
onAggregatePressureWarning: { [weak self] _ in
self?.postAggregateMemoryPressureWarning()
}
)
)
Expand All @@ -60,71 +58,12 @@ extension AppDelegate {
NotificationCacheMemoryPressureResponder(store: notificationStore)
)
}
monitor.onPersistentCriticalPressure = { [weak self] snapshot in
self?.postPersistentCriticalMemoryPressureWarning(snapshot: snapshot)
}
monitor.onAggregatePressureCleared = {
AgentHibernationController.shared.clearAggregateMemoryPressureConfirmations()
}
monitor.start()
}

private func postAggregateMemoryPressureWarning() {
guard let notificationStore else { return }
let managers = paneMemoryGuardrailTabManagers()
guard let tabId = tabManager?.selectedTabId
?? managers.lazy.compactMap({ $0.selectedTabId }).first
?? managers.lazy.compactMap({ $0.tabs.first?.id }).first
else { return }

notificationStore.addNotification(
tabId: tabId,
surfaceId: nil,
title: String(
localized: "memoryPressure.aggregate.title",
defaultValue: "cmux is using substantial aggregate memory"
),
subtitle: String(
localized: "memoryPressure.aggregate.subtitle",
defaultValue: "Idle agent surfaces may be hibernated"
),
body: String(
localized: "memoryPressure.aggregate.body",
defaultValue: "macOS reports memory pressure across cmux and its child processes. Only hidden, idle agent surfaces are considered after a confirmation window; active or visible work is left alone."
),
cooldownKey: "memory-pressure-aggregate",
cooldownInterval: 300
)
}

private func postPersistentCriticalMemoryPressureWarning(snapshot: MemoryPressureSnapshot) {
guard let notificationStore else { return }
let managers = paneMemoryGuardrailTabManagers()
guard let tabId = tabManager?.selectedTabId
?? managers.compactMap({ $0.selectedTabId }).first
?? managers.flatMap({ $0.tabs }).first?.id
else { return }

notificationStore.addNotification(
tabId: tabId,
surfaceId: nil,
title: String(
localized: "memoryPressure.critical.title",
defaultValue: "cmux is under critical memory pressure"
),
subtitle: String(
localized: "memoryPressure.critical.subtitle",
defaultValue: "Hidden renderers and browsers were released"
),
body: String(
localized: "memoryPressure.critical.body",
defaultValue: "macOS is reporting sustained critical memory pressure. cmux has shed hidden resources; close idle workspaces or restart cmux if pressure continues."
),
cooldownKey: "memory-pressure-critical",
cooldownInterval: 300
)
}

private func paneMemoryGuardrailTabManagers() -> [TabManager] {
var managers: [TabManager] = []
var seen: Set<ObjectIdentifier> = []
Expand Down
4 changes: 4 additions & 0 deletions cmux.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,7 @@
7490C0037490C0037490C003 /* MemoryPressureFootprintThresholds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490D0037490D0037490D003 /* MemoryPressureFootprintThresholds.swift */; };
7490C0047490C0047490C004 /* MemoryPressureMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490D0047490D0047490D004 /* MemoryPressureMonitor.swift */; };
7490A0017490A0017490A001 /* MemoryPressureMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490A0027490A0027490A002 /* MemoryPressureMonitorTests.swift */; };
126510000000000000000001 /* MemoryPressureNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 126510000000000000000002 /* MemoryPressureNotificationTests.swift */; };
7490C0057490C0057490C005 /* MemoryPressureResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490D0057490D0057490D005 /* MemoryPressureResponder.swift */; };
7490C0067490C0067490C006 /* MemoryPressureResponderRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490D0067490D0067490D006 /* MemoryPressureResponderRegistry.swift */; };
7490C0097490C0097490C009 /* MemoryPressureSeverity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7490D0097490D0097490D009 /* MemoryPressureSeverity.swift */; };
Expand Down Expand Up @@ -5791,6 +5792,7 @@
7490D0037490D0037490D003 /* MemoryPressureFootprintThresholds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MemoryPressureFootprintThresholds.swift; sourceTree = "<group>"; };
7490D0047490D0047490D004 /* MemoryPressureMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MemoryPressureMonitor.swift; sourceTree = "<group>"; };
7490A0027490A0027490A002 /* MemoryPressureMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryPressureMonitorTests.swift; sourceTree = "<group>"; };
126510000000000000000002 /* MemoryPressureNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryPressureNotificationTests.swift; sourceTree = "<group>"; };
7490D0057490D0057490D005 /* MemoryPressureResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MemoryPressureResponder.swift; sourceTree = "<group>"; };
7490D0067490D0067490D006 /* MemoryPressureResponderRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MemoryPressureResponderRegistry.swift; sourceTree = "<group>"; };
7490D0097490D0097490D009 /* MemoryPressureSeverity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MemoryPressureSeverity.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -10918,6 +10920,7 @@
A7206B020000000000000001 /* SystemAppearanceObserverTests.swift */,
6313FACE0000000000000001 /* PaneMemoryGuardrailTests.swift */,
7490A0027490A0027490A002 /* MemoryPressureMonitorTests.swift */,
126510000000000000000002 /* MemoryPressureNotificationTests.swift */,
C0DE10510000000000000002 /* MobileHostServiceSettingsTests.swift */,
46438523E2764A18B697CC4B /* ManagedPolicySettingsImportTests.swift */,
F0128D58A0B34955A144D263 /* ManagedPolicyRemoteControlTests.swift */,
Expand Down Expand Up @@ -15258,6 +15261,7 @@
D6069002D6069002D6069002 /* MarkdownMermaidZoomTests.swift in Sources */,
D3664002D3664002D3664002 /* MarkdownPanelTests.swift in Sources */,
7490A0017490A0017490A001 /* MemoryPressureMonitorTests.swift in Sources */,
126510000000000000000001 /* MemoryPressureNotificationTests.swift in Sources */,
606500010000000000000001 /* MenuBarOnlyActivationPolicyTests.swift in Sources */,
C0DE70520000000000000002 /* MenuBarProfilingLauncherTests.swift in Sources */,
6AA2F2E8BEB19ED6B8E125DB /* MobileHostAuthorizationTests.swift in Sources */,
Expand Down
Loading
Loading