Skip to content

fix: hide UI immediately on quit to prevent freeze#30

Merged
GeiserX merged 2 commits intomainfrom
fix/clean-quit-ui
Apr 16, 2026
Merged

fix: hide UI immediately on quit to prevent freeze#30
GeiserX merged 2 commits intomainfrom
fix/clean-quit-ui

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented Apr 16, 2026

Summary

  • Hides all windows (popover, settings) immediately at the start of applicationWillTerminate so the app visually disappears before route cleanup runs
  • Previously, the 5-second semaphore wait for async cleanup blocked the main thread, causing the UI to freeze before disappearing

Test plan

  • Open the menu bar popover and click the quit button
  • Verify the popover disappears instantly (no freeze)
  • Verify routes are still cleaned up properly after quit

Summary by CodeRabbit

  • Bug Fixes
    • App now immediately hides all windows when quitting for a cleaner, more responsive shutdown.
    • Shutdown sequence no longer blocks UI; network monitoring and timers are stopped without freezing the app.
    • Route/hosts cleanup runs asynchronously and completion defers final termination until cleanup finishes, ensuring a safe and reliable exit.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7280bdf2-16f1-493b-9460-2f4582949cf2

📥 Commits

Reviewing files that changed from the base of the PR and between d658f21 and ad898fb.

📒 Files selected for processing (1)
  • Sources/VPNBypassApp.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/VPNBypassApp.swift

📝 Walkthrough

Walkthrough

Replaced applicationWillTerminate(_:) with applicationShouldTerminate(_:) -> NSApplication.TerminateReply. On quit the app orders all NSApp.windows out, cancels network/timers, then runs RouteManager.shared.cleanupOnQuit() asynchronously on the @MainActor, deferring termination by returning .terminateLater and calling NSApp.reply(toApplicationShouldTerminate: true) when cleanup completes. Removed semaphore-based blocking.

Changes

Cohort / File(s) Summary
Application termination flow
Sources/VPNBypassApp.swift
Replaced applicationWillTerminate(_:) with applicationShouldTerminate(_:) -> NSApplication.TerminateReply. Immediately hides windows, cancels networkMonitor and timers, invokes RouteManager.shared.cleanupOnQuit() asynchronously on @MainActor, returns .terminateLater, and calls NSApp.reply(toApplicationShouldTerminate: true) when cleanup finishes; removed previous DispatchSemaphore blocking and timeout.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the summary and test plan but lacks the Type of Change and Testing checkboxes required by the template. Add Type of Change section (marking it as a bug fix) and complete the Testing and Checklist sections from the template.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing UI freeze on quit by hiding windows immediately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clean-quit-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/VPNBypassApp.swift (1)

115-121: ⚠️ Potential issue | 🔴 Critical

Main thread deadlock during quit cleanup.

Line 121 creates a deadlock: the main thread blocks on semaphore.wait() while cleanupOnQuit() (a @MainActor method) cannot run because it is the main thread. Since RouteManager is @MainActor, all work in cleanupOnQuit() and its callees (removeAllRoutes(), XPC calls, property mutations) must execute on the main actor. The blocked thread prevents the task from ever running.

Use the proper async termination flow: implement applicationShouldTerminate(_:), return .terminateLater, and call reply(toApplicationShouldTerminate:) after cleanup completes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/VPNBypassApp.swift` around lines 115 - 121, The current quit path
blocks the main thread with a DispatchSemaphore while launching Task { await
RouteManager.shared.cleanupOnQuit() }, causing a deadlock because
cleanupOnQuit() is `@MainActor`; remove the semaphore-based blocking and instead
implement applicationShouldTerminate(_:) to return .terminateLater, call
RouteManager.shared.cleanupOnQuit() (awaiting it on the main actor), and when it
completes invoke reply(toApplicationShouldTerminate:) to allow termination;
replace the semaphore/Task pattern in VPNBypassApp.swift with this async
termination flow so all `@MainActor` work (cleanupOnQuit, removeAllRoutes, XPC
calls, property mutations) runs on the main actor before replying.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/VPNBypassApp.swift`:
- Around line 115-121: The current quit path blocks the main thread with a
DispatchSemaphore while launching Task { await
RouteManager.shared.cleanupOnQuit() }, causing a deadlock because
cleanupOnQuit() is `@MainActor`; remove the semaphore-based blocking and instead
implement applicationShouldTerminate(_:) to return .terminateLater, call
RouteManager.shared.cleanupOnQuit() (awaiting it on the main actor), and when it
completes invoke reply(toApplicationShouldTerminate:) to allow termination;
replace the semaphore/Task pattern in VPNBypassApp.swift with this async
termination flow so all `@MainActor` work (cleanupOnQuit, removeAllRoutes, XPC
calls, property mutations) runs on the main actor before replying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 91c14789-dc43-4ad9-9cca-75e292b07818

📥 Commits

Reviewing files that changed from the base of the PR and between 9db64cb and d658f21.

📒 Files selected for processing (1)
  • Sources/VPNBypassApp.swift

Use applicationShouldTerminate with terminateLater instead of blocking
the main thread with a semaphore. Since RouteManager is @mainactor,
the previous approach deadlocked — cleanup never ran, just timed out.
@GeiserX GeiserX merged commit 0733c66 into main Apr 16, 2026
7 checks passed
@GeiserX GeiserX deleted the fix/clean-quit-ui branch April 16, 2026 15:10
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.

1 participant