Skip to content

fix: drop apps that quit, and reattach apps whose actor gave up - #641

Open
joonhoekim wants to merge 2 commits into
acsandmann:mainfrom
joonhoekim:pr/app-quit-and-reattach
Open

joonhoekim wants to merge 2 commits into
acsandmann:mainfrom
joonhoekim:pr/app-quit-and-reattach

Conversation

@joonhoekim

Copy link
Copy Markdown
Contributor

Problem

Two ways rift loses track of an app, both silently.

  1. A quit app is never dropped. By the time kEventAppTerminated arrives,
    GetProcessPID no longer resolves the terminated process's serial number,
    so the event is dropped. The app stays in rift-cli query applications
    with its actor and AX observer: TextEdit after Quit, or any app killed with
    SIGTERM.
  2. An app busy when its actor starts is never managed. init waits two
    seconds to register the app's AX notifications. If the app refuses AX
    requests for that long, the actor exits, and only activating the app
    (retry_unattached_app) reattaches it. After a rift restart, the app's
    windows in other workspaces are still where the previous rift parked them,
    off screen and out of the overview, so nothing shows them to activate. Seen
    with Chrome and Brave after a restart: missing from query applications
    for hours until activated. The failed init is inferred (logging was at the
    default level); this change logs Could not attach a running app; trying again.

Reproduce

  1. Launch TextEdit and quit it. rift-cli query applications still lists it.
  2. The second can't be triggered at will: restart rift while a busy Chromium
    browser has windows in a workspace that is not shown. When its actor gives
    up, the app never shows up in query applications.

Fix

  • Commit 1: when a termination's pid cannot be read, each active app that
    NSRunningApplication no longer reports as running is terminated as
    AppTerminated would terminate it (WmEvent::AppsMayHaveTerminated).
  • Commit 2: AppLifecycle::sync matches actors to the running apps in both
    directions: running regular apps with no actor are attached, and actors
    whose app no longer runs are terminated (the check from commit 1). It runs
    on app activation, deactivation and space state updates (at most once a
    second), and when a retry falls due: an actor that gives up while its app
    runs is retried after 2 s, doubling up to 5 min.

Testing

  • New tests in wm_controller.rs (app_lifecycle_tests).
  • cargo test --lib: 888 passed. The 2 failures also fail on main (fixed
    separately in test: keep the window server's Space window lists out of unit tests #640).
  • cargo +nightly fmt --all --check.
  • By hand on macOS 27.0.1: TextEdit and an app killed with SIGTERM drop out of
    query applications. I could not make an app refuse AX on demand, so the
    retry path itself is covered by the unit tests only.

By the time kEventAppTerminated arrives, GetProcessPID no longer resolves
the terminated process's serial number, so the event was dropped without
a word and no app was ever terminated: a quit app stayed in rift's list
(rift-cli query applications) with its app actor and AX observer, for
TextEdit after Quit as for an app killed with SIGTERM.

When a termination's pid cannot be read, each active app that
NSRunningApplication no longer reports as running is now terminated as
AppTerminated would.
An app busy when its actor started refused AX requests for the two seconds
init waits, and the actor exited. Only activating the app reattached it,
and its windows stayed where the last rift had parked them, off screen and
out of the overview, so nothing showed it to activate. Seen with Chrome and
Brave after a restart: absent from rift-cli query applications for hours.

AppLifecycle::sync now matches actors to the running apps in both
directions: running regular apps with no actor are attached, and actors
whose app no longer runs are terminated, the check AppsMayHaveTerminated
already made. It runs on app activation and deactivation and on space
state updates, at most once a second, and when a retry falls due: an actor
that gives up while its app runs is retried after 2 seconds, doubling up to
5 minutes. An actor marks its handle once init succeeds, so a failure after
it had attached starts the retries over.
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