[Bug]: Linux AppImage t3 launcher: a SIGTERM to the process group unmounts the AppImage under t3 serve, which dies with SIGBUS#24416
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Steps to reproduce
Linux AppImage, 0.1.1-nightly.20261020.3438, using the t3 command the app installs at ~/.t3/bin/t3 (generated by apps/desktop/src/app/DesktopCliShim.ts).
- Start a server the way a test script or supervisor usually does, in its own process group:python
1 p = subprocess.Popen(["t3", "serve", "--base-dir", d, "--host", "127.0.0.1", "--port", "45817", "--no-browser"], start_new_session=True) - Wait until it is ready, then stop the group:
os.killpg(p.pid, signal.SIGTERM). - The server dies with SIGBUS (
BUS_ADRERR, often in aV8Workerthread) and systemd-coredump writes a 20-45 MB core.
kill -TERM -<pgid> from a shell does the same. SIGHUP to the group (closing a terminal) has the same exposure.
Expected behavior
The server shuts down cleanly, then the launcher unmounts the AppImage.
Actual behavior
The launcher runs "$app" --appimage-mount & as a background job in its own process group. A group signal reaches that mounter at the same moment as the server. The mounter unmounts /tmp/.mount_T3-Cod* right away, while the server is still shutting down and paging in code from the mount, so the server takes SIGBUS.
Ctrl-C is not affected, because background jobs of a non-interactive sh ignore SIGINT (the comment in DesktopCliShim.ts relies on this). SIGTERM and SIGHUP are not ignored.
On one machine this produced 24 SIGBUS cores in about 3 hours from an automated UX test loop. One core dump took 5 minutes to process and failed with a timeout.
Proposed fix
Start the mounter in its own session so group signals miss it, and also trap HUP so the EXIT trap still unmounts after the server exits:
| 1 | - '"$app" --appimage-mount >"$work/mount" 2>/dev/null &', |
| 2 | + 'setsid "$app" --appimage-mount >"$work/mount" 2>/dev/null &', |
| 3 | "mounter=$!", |
| 4 | `trap 'kill "$mounter" 2>/dev/null; rm -rf "$work"' EXIT`, |
| 5 | - "trap 'exit 130' INT TERM", |
| 6 | + "trap 'exit 130' INT TERM HUP", |
setsid is part of util-linux. The AppImage branch is Linux-only. Because the script runs without job control, the background child is not a group leader, so setsid execs in place and $! is still the mounter's PID.
Tested with the patched launcher against the original:
| Case | Original | Patched |
|---|---|---|
| group SIGTERM | SIGBUS core | clean, no core, nothing left mounted |
| group SIGINT | clean | clean |
| group SIGHUP | not run | clean, exit 130 |
| SIGTERM to launcher only | no core (server keeps running until it exits) | same |
| normal exit / error exit | status passes through | status passes through |
In every patched case no --appimage-mount process and no /tmp/.mount_T3-Cod* was left behind.
One trade-off: if the whole group gets SIGKILL, the mounter now survives, because nothing runs to kill it. Before the change it died with the group. That case already leaked the mount when only the launcher got SIGKILL.
Related but different: #14259 (the desktop main process exits and takes the mount with it).