Skip to content

fix(macOS): keep Tor/BLE alive when minimized - #1633

Open
Chessing234 wants to merge 3 commits into
permissionlesstech:mainfrom
Chessing234:fix/macos-keep-alive-when-minimized
Open

fix(macOS): keep Tor/BLE alive when minimized#1633
Chessing234 wants to merge 3 commits into
permissionlesstech:mainfrom
Chessing234:fix/macos-keep-alive-when-minimized

Conversation

@Chessing234

@Chessing234 Chessing234 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Hold a ProcessInfo activity 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:

  • Dropped applicationShouldTerminateAfterLastWindowClosed → false and 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 deprecated activate(ignoringOtherApps:) call and the flaky WindowGroup + applicationShouldHandleReopen path.
  • Gated the assertion on a transport actually relaying, rather than holding it 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 the assertion is released.

Shape

RelayActivityAssertion holds the policy and the begin/end bookkeeping and imports only Foundation. MacRelayActivityController owns the Combine subscriptions to NetworkActivationService, TorManager.isReady, and ChatViewModel.bluetoothState. Splitting them keeps the part worth testing free of AppKit and Combine.

torDesired torReady bluetoothPoweredOn assertion
any true held
false false false released

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 real RelayActivityAssertion.swift.
  • Typechecked the changed files under -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 xcodebuild cannot 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

@jackjackbits jackjackbits left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. applicationShouldTerminateAfterLastWindowClosed → false needs 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.
  2. The reopen path needs device verification. Reopen-after-last-window-close via SwiftUI WindowGroup + applicationShouldHandleReopen is a known-flaky combination, and activate(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.

@Chessing234
Chessing234 force-pushed the fix/macos-keep-alive-when-minimized branch from b89abb0 to 50c9cd2 Compare August 11, 2026 09:52
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.
@Chessing234
Chessing234 force-pushed the fix/macos-keep-alive-when-minimized branch from 50c9cd2 to 665d552 Compare August 11, 2026 09:57
@Chessing234

Copy link
Copy Markdown
Contributor Author

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.

@Chessing234
Chessing234 force-pushed the fix/macos-keep-alive-when-minimized branch from 665d552 to 9be1b30 Compare August 11, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Persistent Tor connection on macOS when minimized

2 participants