Skip to content

fix: quit handler cleanup ordering and timeout test coverage#689

Merged
pedramamini merged 1 commit intomainfrom
fix/quit-handler-followup
Mar 29, 2026
Merged

fix: quit handler cleanup ordering and timeout test coverage#689
pedramamini merged 1 commit intomainfrom
fix/quit-handler-followup

Conversation

@pedramamini
Copy link
Copy Markdown
Collaborator

@pedramamini pedramamini commented Mar 29, 2026

Summary

Follow-up to #677.

Test plan

Summary by CodeRabbit

  • Bug Fixes

    • Corrected the shutdown sequence to ensure system resources are cleared at the proper time during application termination.
  • Tests

    • Added test coverage for quit timeout behavior, including scenarios where the renderer fails to respond, confirms, or cancels a quit operation.

…rt whitespace

Follow-up to PR #677 (quit confirmation safety timeout):

- Move powerManager.clearAllReasons() after processManager.killAll() to
  prevent late process output from re-arming the sleep blocker
- Add tests for the 5s safety timeout (force-quit, clear on confirm,
  clear on cancel)
- Assert killAll/clearAllReasons call ordering
- Revert whitespace-only changes to docs/releases.md
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The quit-handler implementation reorders power management cleanup to occur after process termination instead of before. The test suite is updated to enforce call sequencing and adds three new test cases validating safety timeout behavior when quit confirmation is delayed, confirmed, or cancelled by the renderer.

Changes

Cohort / File(s) Summary
Quit Handler Implementation
src/main/app-lifecycle/quit-handler.ts
Reordered cleanup sequencing: moved powerManager.clearAllReasons() call to execute after processManager?.killAll() rather than before grooming-session cleanup, ensuring OS-level sleep prevention remains active during process termination.
Quit Handler Tests
src/__tests__/main/app-lifecycle/quit-handler.test.ts
Updated confirmed-quit test to assert call ordering (killAll before clearAllReasons). Added three new test cases with fake timers: (1) 5-second timeout triggers mockQuit() and logs warning when renderer never responds, (2) renderer confirmation via IPC prevents second mockQuit() after timeout, (3) renderer cancellation via IPC prevents mockQuit() after timeout.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 A hop through the quit-handler's dance,
Where timers tick and processes prance,
Power sleeps only when all tasks are done,
Safety timeouts ensure none's left to run,
Order restored, the app bids goodbye!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the two main changes: fixing the quit handler cleanup ordering and adding timeout test coverage, matching the PR's core objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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/quit-handler-followup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@pedramamini pedramamini merged commit 9e53fe5 into main Mar 29, 2026
4 of 5 checks passed
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR makes two targeted improvements to the quit-handler subsystem as a follow-up to #677.

Source change (quit-handler.ts): powerManager.clearAllReasons() is moved to execute after processManager.killAll() in performCleanup(). The previous ordering left a window where a dying process could call addBlockReason() between the clearAllReasons() call and the actual process death, silently re-arming the OS-level sleep blocker. The new order is correct: kill all processes first so no new block reasons can be added, then clear them.

Test change (quit-handler.test.ts):

The logic is sound and the new tests are well-structured. Two minor style suggestions are noted:

  • vi.useRealTimers() is not guarded with try/finally or a shared afterEach, so a failing assertion in a timer test could leave the fake-timer state active for later tests.
  • The constant QUIT_CONFIRMATION_TIMEOUT_MS = 5000 is repeated verbatim in all three new tests; exporting it from the source would prevent silent divergence if the value changes.

Confidence Score: 5/5

Safe to merge — the source change is a one-line reorder with clear correctness rationale, and the new tests solidify coverage of the timeout path.

Both findings are P2 style/maintenance suggestions (fake-timer cleanup guard and exporting the timeout constant). Neither represents a correctness defect or data-integrity risk. All remaining concerns are non-blocking.

No files require special attention; the minor suggestions are in the test file only.

Important Files Changed

Filename Overview
src/main/app-lifecycle/quit-handler.ts Moves powerManager.clearAllReasons() after processManager.killAll() in performCleanup() to prevent killed processes from re-arming the sleep blocker; change is minimal and logically sound.
src/tests/main/app-lifecycle/quit-handler.test.ts Adds three new timer-based tests for the 5s safety timeout path and an invocation-order assertion for killAll → clearAllReasons; minor concern: fake-timer cleanup is not guarded against test failure.

Sequence Diagram

sequenceDiagram
    participant U as User (Cmd+Q)
    participant E as Electron app
    participant R as Renderer
    participant PM as ProcessManager
    participant PW as PowerManager

    U->>E: quit attempt
    E->>E: before-quit fired
    E->>R: app:requestQuitConfirmation
    E->>E: arm 5s safety timeout

    alt Renderer confirms (< 5 s)
        R->>E: app:quitConfirmed
        E->>E: clearConfirmationTimeout()
        E->>E: app.quit() → before-quit (confirmed)
        E->>PM: killAll()
        E->>PW: clearAllReasons()
    else Renderer cancels (< 5 s)
        R->>E: app:quitCancelled
        E->>E: clearConfirmationTimeout()
    else Renderer never responds (≥ 5 s)
        E-->>E: timeout fires → force-quit
        E->>E: app.quit() → before-quit (confirmed)
        E->>PM: killAll()
        E->>PW: clearAllReasons()
    end
Loading

Reviews (1): Last reviewed commit: "fix: quit handler followup — reorder cle..." | Re-trigger Greptile

Comment on lines +375 to +396
vi.useFakeTimers();

const { createQuitHandler } = await import('../../../main/app-lifecycle/quit-handler');

const quitHandler = createQuitHandler(deps as Parameters<typeof createQuitHandler>[0]);
quitHandler.setup();

const mockEvent = { preventDefault: vi.fn() };
beforeQuitHandler!(mockEvent);

// Renderer was asked for confirmation
expect(mockMainWindow.webContents.send).toHaveBeenCalledWith('app:requestQuitConfirmation');
expect(mockQuit).not.toHaveBeenCalled();

// Advance past the 5s timeout without renderer responding
vi.advanceTimersByTime(5000);

expect(mockQuit).toHaveBeenCalled();
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('timed out'), 'Window');

vi.useRealTimers();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Fake timers not cleaned up on assertion failure

vi.useRealTimers() is placed at the end of each fake-timer test, but if any expect between vi.useFakeTimers() and vi.useRealTimers() throws, the process-wide timer state is left in fake mode for all subsequent tests. This can cause mysterious hangs or failures in otherwise unrelated tests.

The same pattern appears in all three new tests (lines 374–395, 398–421, 423–443).

Consider centralising the cleanup in an afterEach:

afterEach(() => {
    vi.useRealTimers(); // idempotent when timers are already real
    vi.restoreAllMocks();
});

Then the individual vi.useRealTimers() calls at the end of each test can be removed.

expect(mockQuit).not.toHaveBeenCalled();

// Advance past the 5s timeout without renderer responding
vi.advanceTimersByTime(5000);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Hardcoded timeout value duplicates the source constant

5000 is duplicated in all three new tests (lines 390, 417, 439) but QUIT_CONFIRMATION_TIMEOUT_MS is not exported from quit-handler.ts. If the timeout is ever adjusted, the tests will silently diverge.

Exporting the constant makes the coupling explicit:

// quit-handler.ts
export const QUIT_CONFIRMATION_TIMEOUT_MS = 5000;
// quit-handler.test.ts
import { QUIT_CONFIRMATION_TIMEOUT_MS } from '../../../main/app-lifecycle/quit-handler';
// ...
vi.advanceTimersByTime(QUIT_CONFIRMATION_TIMEOUT_MS);

pedramamini added a commit that referenced this pull request Mar 29, 2026
Bring quit handler safety timeout (#677, #689) and power manager
into RC. Resolve merge conflicts:
- quit-handler.ts: keep both deleteCliServerInfo (RC) and
  powerManager + QUIT_CONFIRMATION_TIMEOUT_MS (main)
- index.ts: keep Cue engine stop (RC) and powerManager +
  stopSessionCleanup (main)
- releases.md: take main's version (more complete changelog)
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