Wait for the backend's destroy before the process exits (needs libtacky#7) #17

Closed
asxpi wants to merge 0 commits from asxpi/anotherim-js:fix/quit-waits-for-backend into develop
First-time contributor

Depends on libtacky#7. The libtacky submodule points at that PR's commit, 5fc35e36, which is only in asxpi/libtacky until #7 is merged. Point it at the merge commit before merging this.

The bug. The desktop app (0.5.4 to 0.5.7, Flatpak) segfaults every time it quits. Closing the window hangs for ~15 s while systemd writes the core dump, and the backend's last store transaction may be lost. Symbolized with the app's debug extension and org.gnome.Sdk.Debug//50:

Thread LWP 48 (crashing): BackendThread -> StopEventProc -> DbTransPostCmd -> sqlite3_exec
  -> sqlite3BtreeCommitPhaseOne -> pagerWalFrames -> walWriteOneFrame -> sqlite3Codec   SIGSEGV
Thread LWP 2 (main): anotherim_tacky::app::run (src/app.rs:756) -> tauri App::run
  -> tao EventLoop::run -> std::process::exit -> __run_exit_handlers

Why. stop_backend (on RunEvent::Exit) drops the backend, which is tacky_destroy. That waits two seconds and returns whatever the destroy is doing. With three accounts the destroy is still running when it returns: they shut down one after another, and the stop waits for a request in flight. Tauri then calls process::exit(), and the backend's commit dies under the exit handlers. The same frame appears at every quit in every build I ran today.

The change.

  • lib.rs binds libtacky's new tacky_destroy_wait and adds Tacky::destroy_waiting(self, max_ms). It's the destroy Drop does, with the host's bound, and frees the sink afterwards in the same way.
  • stop_backend uses it with 10 s (QUIT_WAIT_MS), so a wedged backend still can't hold the quit for good.
  • stop_backend runs on RunEvent::Exit, when the windows are already gone, and before the push-started path's exit(0).
  • Android's headless push worker (headless.rs) keeps tacky_destroy, because its process doesn't exit after it.

Checked in a local Flatpak build of 0.5.7 with this change and libtacky 5fc35e36 on GNOME 50 (Wayland), with three accounts: quitting leaves no core dump, where every quit before did. libtacky's make test-embed-host covers the new call (see #7).

🤖 Generated with Claude Code

**Depends on [libtacky#7](https://dev.another.im/another-im/libtacky/pulls/7).** The `libtacky` submodule points at that PR's commit, `5fc35e36`, which is only in `asxpi/libtacky` until #7 is merged. Point it at the merge commit before merging this. **The bug.** The desktop app (0.5.4 to 0.5.7, Flatpak) segfaults every time it quits. Closing the window hangs for ~15 s while systemd writes the core dump, and the backend's last store transaction may be lost. Symbolized with the app's debug extension and `org.gnome.Sdk.Debug//50`: ``` Thread LWP 48 (crashing): BackendThread -> StopEventProc -> DbTransPostCmd -> sqlite3_exec -> sqlite3BtreeCommitPhaseOne -> pagerWalFrames -> walWriteOneFrame -> sqlite3Codec SIGSEGV Thread LWP 2 (main): anotherim_tacky::app::run (src/app.rs:756) -> tauri App::run -> tao EventLoop::run -> std::process::exit -> __run_exit_handlers ``` **Why.** `stop_backend` (on `RunEvent::Exit`) drops the backend, which is `tacky_destroy`. That waits two seconds and returns whatever the destroy is doing. With three accounts the destroy is still running when it returns: they shut down one after another, and the stop waits for a request in flight. Tauri then calls `process::exit()`, and the backend's commit dies under the exit handlers. The same frame appears at every quit in every build I ran today. **The change.** - `lib.rs` binds libtacky's new `tacky_destroy_wait` and adds `Tacky::destroy_waiting(self, max_ms)`. It's the destroy `Drop` does, with the host's bound, and frees the sink afterwards in the same way. - `stop_backend` uses it with **10 s** (`QUIT_WAIT_MS`), so a wedged backend still can't hold the quit for good. - `stop_backend` runs on `RunEvent::Exit`, when the windows are already gone, and before the push-started path's `exit(0)`. - Android's headless push worker (`headless.rs`) keeps `tacky_destroy`, because its process doesn't exit after it. **Checked** in a local Flatpak build of 0.5.7 with this change and libtacky `5fc35e36` on GNOME 50 (Wayland), with three accounts: quitting leaves no core dump, where every quit before did. libtacky's `make test-embed-host` covers the new call (see #7). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The desktop app segfaulted every time it quit, hanging ~15 s while
the core was written. stop_backend dropped the backend on
RunEvent::Exit, and tacky_destroy returns after two seconds whatever
the destroy is doing; with three accounts it was still running, Tauri
called process::exit(), and its last store commit crashed in
SQLCipher under the exit handlers:

  BackendThread -> StopEventProc -> DbTransPostCmd -> sqlite3_exec
    -> walWriteOneFrame -> sqlite3Codec              SIGSEGV
  main: tao event loop -> std::process::exit -> __run_exit_handlers

stop_backend now stops it with tacky_destroy_wait (libtacky PR #7,
which the submodule points at) through Tacky::destroy_waiting, bounded
at 10 s so a wedged backend cannot hold the quit for good. It runs on
RunEvent::Exit, when the windows are gone, and before the push-started
path's exit. Android's headless push worker keeps tacky_destroy: its
process does not exit after it.

Checked in a Flatpak build of 0.5.7 with this and libtacky 5fc35e36:
quitting leaves no core dump, where every quit did before.

Assisted-by: Claude:opus-5-5
Owner

Merged into develop by hand as 1dedead0 (2026-10-16), with the libtacky submodule pointed at v1.0.0-rc7 (b40975d6), the tag that carries libtacky PR #7 merged, plus its review's fix: Reap had counted each wake-up as 100 ms and every emit woke it, so tacky_destroy's two seconds were really about twenty emits, which is what let a three-account destroy outrun it (the story in the description needs no slow destroy or request in flight); the bound is a clock deadline now. Your 10 s bound and destroy_waiting are kept as they are. Ships in 0.5.8. Thank you.

Merged into develop by hand as 1dedead0 (2026-10-16), with the libtacky submodule pointed at v1.0.0-rc7 (b40975d6), the tag that carries libtacky PR #7 merged, plus its review's fix: Reap had counted each wake-up as 100 ms and every emit woke it, so tacky_destroy's two seconds were really about twenty emits, which is what let a three-account destroy outrun it (the story in the description needs no slow destroy or request in flight); the bound is a clock deadline now. Your 10 s bound and destroy_waiting are kept as they are. Ships in 0.5.8. Thank you.
ann closed this pull request 2026-10-16 11:34:33 +00:00
ann referenced this pull request from a commit 2026-10-27 13:09:52 +00:00
The remaining Lows of the final anotherim-js audit of 2026-10-27 (/data/another-im/audit-2026-10-27-final-anotherim-js.md): XEP-0439 actions are not drawn in rooms (#12: rowActions(m, room), message-row passes showNick, which both callers set from the chat's groupchat; libtacky refuses the press there and any occupant could draw buttons; whether action-selected goes out encrypted in an OMEMO chat is F2's libtacky half), a quick response chip shows the value it sends after its label whenever the two differ but for case and spacing (#13: XEP-0439 sends the value and only shows the label, so the new shownValue keeps the XEP and the reader sees the words before they go), isReportable is false for a room occupant's private chat since Blocking keeps bare JIDs and would block and report the whole room (#17, the safe one of the two choices), a vCard's tel: link carries data-ask and the link guard asks "Call {number}?" / "This opens {number} in your phone or calling app." before the opener in the shells and in a tab alike (#18), the desktop shell's openable hands a TEL: link on as tel: (#21, now a Cow), a gateway's auto-approved contact is added with no roster name instead of the subscribe nick (#19: their nick is their word, the name drawn then comes from their vCard or nick as theirs), and localHost, the page's sticker gate, also refuses a single-label host and names under .local, .localdomain, .home.arpa, .internal, .lan, .home, .intranet, .corp and .private, with the browser edition's remaining gap (a public-looking name resolving to a private address) documented at the function (#20). New strings in en, ru, uk, et and es; CHANGELOG Unreleased has a Security block naming the audit ids. Tests that failed before the fix (9 of them): quick-responses (rowActions in a room, shownValue, the chip showing "Large one Large"), report-spam (PM not reportable), requests (gateway contact added without a name), transfers (localHost local names), os-links (the tel question), links-cov (a data-ask tel opens through the question in a tab), vcard-profiles (tel link has data-ask); links.rs a_tel_scheme_in_upper_case_is_handed_on_in_lower_case. make -o audit check STRICT=1 clean (2110 keys), make test-ui 384 files 4246 passed 8 skipped, make test-shell ok, WebKit smoke 6769 ok with the contact info and profile card screens, its 4 FAILs the stale Android lock-line checks b1bdb10a fixed on develop.

Pull request closed

Sign in to join this conversation.
No description provided.