fix(macOS): keep Tor/BLE alive when minimized - #1633
Conversation
jackjackbits
left a comment
There was a problem hiding this comment.
The diagnosis is credible — main's dormancy handling is iOS-only, and nothing stops App Nap from freezing Tor/BLE when the macOS window is minimized or occluded (#1593). beginActivity(.userInitiatedAllowingIdleSystemSleep) is the standard mitigation, and everything is correctly fenced under #if os(macOS).
Blockers:
applicationShouldTerminateAfterLastWindowClosed → falseneeds to be opt-in. As written, closing the window silently keeps Tor running and BLE broadcasting your nickname — beyond what #1593 asked for ("minimized to dock"), and against this app's privacy posture (panic gestures, privacy screen). Someone who closes bitchat will reasonably believe it stopped. Either scope this PR to the minimize/occlusion case only, or add an explicit "keep running when window is closed" setting that defaults off.- The reopen path needs device verification. Reopen-after-last-window-close via SwiftUI
WindowGroup+applicationShouldHandleReopenis a known-flaky combination, andactivate(ignoringOtherApps:)is deprecated on macOS 14+. Your own test plan is entirely unchecked — please run it on hardware and report results in the PR.
Nit: the activity is held unconditionally for process lifetime — gate it on Tor/BLE actually being active so an idle configuration doesn't hold the assertion.
b89abb0 to
50c9cd2
Compare
App Nap freezes timers, sockets and BLE callbacks once every window is minimized or fully occluded, which stalls Tor and the mesh relay even though the process is still running (permissionlesstech#1593). iOS has scene-phase dormancy handling; macOS had none. Scoped to the minimize/occlusion case: closing the last window still quits, so "I closed bitchat" continues to mean the radios stop. The exemption is gated on a transport actually relaying rather than held for process lifetime — Tor switched on (including bootstrap, which needs CPU before isReady flips) or Bluetooth powered on. With Tor off and Bluetooth unavailable there is nothing to relay, so an assertion there is pure battery cost. Policy and begin/end bookkeeping live in RelayActivityAssertion, free of Combine and AppKit so both are directly testable; MacRelayActivityController holds the transport subscriptions.
Covers the full transport truth table plus the bookkeeping a leak would otherwise only reveal in Instruments: no assertion while idle, begin exactly once, no stacking when a second transport comes up, hold through losing one transport, release with the last, re-arm after a radio cycles, and idempotent release.
50c9cd2 to
665d552
Compare
|
reworked — dropped the last-window-close and dock-reopen changes, so closing the window quits exactly like main does and the deprecated activate(ignoringOtherApps:) path goes with them. the assertion is now gated on a transport actually relaying (tor on including bootstrap, or bluetooth powered on) rather than held for process lifetime, and the policy plus begin/end bookkeeping moved into RelayActivityAssertion so it's testable without appkit — 16 cases over the truth table and the lifecycle (no double-begin, release only with the last transport, re-arm after a radio cycles). no xcode on this machine, so i couldn't run the xcode suite or check hardware — left those boxes unticked rather than claiming them. if you want a measured minimize/relay run before this lands i'll get to a machine that has it. the red app-test check is timeoutRestoredSessionDefersQueueDrainUntilConvergence, which also failed on main in run 31418625613 on the 10th. can't rerun it without admin. |
665d552 to
9be1b30
Compare
Summary
Hold a
ProcessInfoactivity so App Nap cannot throttle Tor and the BLE mesh while the macOS window is minimized or fully occluded (#1593).Reworked after review — the scope is now the minimize/occlusion case only:
applicationShouldTerminateAfterLastWindowClosed → falseand the dock-reopen handler. Closing the last window quits exactly as it does on main, so stopping bitchat still stops the radios. This also removes the deprecatedactivate(ignoringOtherApps:)call and the flakyWindowGroup+applicationShouldHandleReopenpath.isReadyflips) or Bluetooth powered on. With Tor off and Bluetooth unavailable there is nothing to relay, so the assertion is released.Shape
RelayActivityAssertionholds the policy and the begin/end bookkeeping and imports only Foundation.MacRelayActivityControllerowns the Combine subscriptions toNetworkActivationService,TorManager.isReady, andChatViewModel.bluetoothState. Splitting them keeps the part worth testing free of AppKit and Combine.Verification
Ran locally:
RelayActivityAssertionTests— 16 cases / 33 assertions: the full transport truth table, plus the bookkeeping a leak would otherwise only reveal in Instruments (begin exactly once, no stacking when a second transport comes up, hold through losing one transport, release with the last, re-arm after a radio cycles, idempotent release). Compiled and run against the realRelayActivityAssertion.swift.-swift-version 5,-swift-version 6, and-strict-concurrency=complete.Not run locally: the Xcode test suite and any on-device check — this machine has Command Line Tools only, no Xcode, so
xcodebuildcannot build the app here. CI covers the build, the app test target, the iOS simulator tests, SwiftLint, and the periphery scan.I have not verified the App Nap behavior on macOS hardware, so I have not ticked that box. If you would rather see a measured before/after (minimize, confirm relay traffic continues) before this lands, say so and I will find a machine with Xcode.
Closes #1593