perf: onedir backend takes warm desktop launches from 20s to 2s - #6
Merged
Conversation
Onefile extracted a 39MB archive to a fresh temp path on every launch, so macOS re-validated every native library's code signature every time - the validation cache is keyed by path, and the path never repeated. That was the ~20s that remained after dropping matplotlib, and it was paid on every single launch, forever. The backend now freezes with PyInstaller's onedir layout and ships at a stable path inside the bundle, so signature validation is paid once per install or update. A directory cannot go through tauri.conf.json's externalBin - that contract wants one file per target triple - so the tree is staged under frontend/src-tauri/resources/backend/ and declared in the resources map; the shell resolves the executable through resource_dir() and spawns it with the same tauri-plugin-shell Command machinery as before. Both desktop workflows already run build_sidecar.py before tauri build, so CI needs no changes. Onedir also removes PyInstaller's bootloader middle layer: the process the shell spawns IS the Python server, so CommandChild::kill() reaches it directly. The orphan class fixed by the parent-pid watchdog cannot occur on the normal quit path anymore; the watchdog stays for what no exit handler can cover (crash, force quit). Measured on the packaged .app (aarch64-darwin): - warm launch, open to backend ready: 2.2s (was 37-39s originally, 20-22s after dropping matplotlib) - backend process alone, warm: 0.6-0.8s - first-ever launch (cold caches): ~23s, once per install/update - after an OS reboot: 3.3s - the validation cache survives reboots, so only installs and updates pay the cold price - two instances: ports 3354 + OS-assigned, both serving - clean quit: all processes exit, no orphan, port released - SIGKILL on the shell: watchdog removes the backend within seconds Disk cost: the staged tree is 115M against the 39M onefile archive - the uncompressed layout is exactly what makes launches fast, and installers compress it back down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 2 of the startup plan — the other half of the 37s, after #4 removed the matplotlib half.
Why onefile was slow forever
macOS caches code-signature validation by path. Onefile extracts its 39MB archive to a fresh
_MEItemp path on every launch, so every native library in the scientific stack was re-validated every time — ~20s, on every launch, with no way to ever get faster.Onedir ships the tree at a stable path inside the bundle, so validation is paid once per install/update.
How it ships
externalBinwants one file per target triple, so a directory can't go through it. Instead:build_sidecar.pyfreezes with--onedirand stages the tree tofrontend/src-tauri/resources/backend/(tests rewritten: staging, exec bit, stale-tree replacement, missing-executable error).tauri.conf.jsondeclares it in theresourcesmap.lib.rsresolves the executable viaresource_dir()and spawns it with the sametauri-plugin-shellCommand machinery — the port/parent-pid/log-forwarding code is untouched.Both desktop workflows already run
build_sidecar.pybeforetauri build, so CI needs zero changes. (tauri-buildvalidates the resource path at compile time, so a barecargo checknow needs the backend staged first — same de-facto requirementexternalBinhad.)Bonus: the orphan-process root cause is gone
Onedir has no bootloader middle layer — the spawned process is the Python server, so
CommandChild::kill()reaches it directly. The orphan class #3 worked around can no longer occur on the normal quit path; the parent-pid watchdog stays for crash/force-quit, where no exit handler runs.Measured on the packaged .app (aarch64-darwin)
An unplanned bonus test: the machine rebooted mid-verification with two instances running — on login, macOS auto-relaunched the app and the whole port chain came up correctly.
Disk: staged tree is 115M vs the 39M onefile archive; the uncompressed layout is exactly what makes launches fast, and installers compress it back down.
Backend suite: 382 passed.
Remaining from the original plan: a frontend "backend starting" indicator to cover the one-time cold launch; that's small and separate.
🤖 Generated with Claude Code