Fix iOS cmux-tui topology reconnect (#21001)

Merged
AU
21d ago
+603-60

Summary

A transient cmux-tui control disconnect no longer permanently stops iOS topology observation. The provider now reconnects its existing session control with a single bounded backoff loop, keeps stable terminal ownership, and forces a fresh workspace snapshot after a generation gap so stale rows cannot win a race.

Testing

  • python3 scripts/verify-local.py --all — passed.
  • python3 scripts/verify-local.py --only swift-syntax --swift-changed origin/main — passed (4 files).
  • python3 scripts/verify-local.py --only test-wiring — passed.
  • ./scripts/lint-ios-package-conventions.sh — passed with existing repository warnings only.
  • python3 scripts/localization_defaults.py — passed (0 mismatches).
  • Hosted iOS package, simulator build, iPhone, iPad, and aggregate iOS tests passed for the latest head.
  • Added provider-level stale-generation and bounded-churn tests; reconnect watcher handoff and post-open subscription backoff are covered.
  • python3 scripts/check-workspace-package-groups.py --check — passed.
  • python3 scripts/lint-feature-flags.py — passed.
  • Focused swift test --package-path Packages/iOS/CmuxMobileShell --filter MobileSSHCmuxTUITopologyReconnectTests was not run: the prescribed nx-remote host session failed before job start because cmux-lawrence-2 DNS was unavailable. The PR iOS package lane is the required compile/test evidence.

Issues

Issue lifecycle

Impact map

  • Source of truth and owner: MobileSSHCmuxTUIProvider owns one reconnect coordinator and one active CmuxTUIControl per session; CmuxTUIControl.subscribe() remains the event source.
  • Direct callers and sibling paths inspected: MobileSSHHostProviders, MobileSSHComputers, and the separate tmux topology/restart provider path; cmux-tui workspace and terminal resource IDs remain the ownership keys.
  • Cross-surface effects: reconnect stays within the cached provider/session, closes superseded controls before installing a replacement, and cancels observation/backoff from close(); no UI strings, localization, persistence schema, or release metadata changed.
  • Tests and evidence: the new package tests simulate disconnect → automatic control replacement → later topology event, assert capped backoff, and prove stale generations cannot clear or admit a newer snapshot.
  • Residual risk: live SSH transport behavior still needs the hosted iOS package lane and the affected iOS connectivity-soak workload; no local app build was run because Swift jobs must use the hosted route.

Proof

No visual proof applies; this is an iOS connectivity and topology lifecycle change. Hosted iPhone and iPad simulator lanes exercise the affected package and test target.

Checklist

  • Reconnect ownership stays single-flight and closes superseded controls.
  • Stale generations cannot admit events or clear newer snapshots.
  • Recovery and generation-stability retries are bounded and cancellation-aware.
  • Hosted iOS package, build, simulator, and aggregate test lanes are required before merge.

Changelog

Fixed: iOS cmux-tui topology observation now reconnects after a transient control transport disconnect and refreshes from a current workspace snapshot.

Mergeability


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes iOS cmux-tui topology observation so a transient control transport disconnect no longer permanently stops updates. The provider now reconnects its session control with a bounded backoff loop, shares in-flight reconnect attempts among concurrent callers, keeps stable terminal ownership, and forces a fresh workspace snapshot after a generation gap so stale rows cannot win a race.

  • Reconnecting caps retry delay at 30 seconds and stops after three attempts so dead owners fall out of discovery; a control is replaced only after the old one is closed, and backoff sleeps are injectable for fast tests.
  • Added an explicit handshake with a client name and 30-second timeout on session connect and 10-second on socket connect.
  • Tracks control generations so events and list results from superseded controls are discarded, and listWorkspaces retries up to three times when the generation changes mid-request before failing the snapshot.
  • Adds package tests covering disconnect → reconnection, bounded backoff, dead-owner recovery exhaustion, stale-generation gating, and snapshot retry bounds.

Closes #21001.

Summary by CodeRabbit

  • Reliability
    • The mobile SSH interface reconnects automatically after control-channel interruptions and resumes receiving workspace updates when the connection is restored.
    • Reconnection attempts use increasing delays, up to 30 seconds, and stop after a limited number of tries.
    • Failed operations trigger a reconnection attempt and are retried once.
    • Workspace listings retry if the connection changes mid-request, helping ensure results reflect a consistent connection.
    • Updates from replaced connections are ignored, and a fresh workspace snapshot is requested when needed.
    • Connection setup uses a bounded handshake timeout.