Skip to content

Update deps, fix workspace hotpatching, add crash recovery#1

Open
enomado wants to merge 6 commits into
lucasmerlin:mainfrom
enomado:main
Open

Update deps, fix workspace hotpatching, add crash recovery#1
enomado wants to merge 6 commits into
lucasmerlin:mainfrom
enomado:main

Conversation

@enomado
Copy link
Copy Markdown

@enomado enomado commented Mar 29, 2026

Summary

  • Update dependencies: egui/eframe 0.32 → 0.34.1, subsecond 0.7.0-rc.0 → 0.7.4
  • Remove dioxus-devtools dependency (causes compile errors via dioxus-core); reimplement devserver connection with dioxus-cli-config + tungstenite + serde_json
  • Fix workspace crate hotpatching — the original workspace_crate_ui was not actually updating on hotpatch
  • Add crash recovery — panic in workspace code shows red error in UI instead of killing the app
  • Comprehensive README with architecture docs

What was broken and why

1. eframe 0.34 API change

App::update()App::ui(), CentralPanel::show()show_inside(). Fixed by updating call sites.

2. dioxus-devtools pulls in dioxus-core which doesn't compile

Replaced with a direct WebSocket connection to dx devserver (~30 lines), using only dioxus-cli-config for endpoint discovery.

3. SIGSEGV on workspace hotpatch (Linux)

dx on Linux only exports main (--export-dynamic-symbol,main). The patch .so couldn't resolve egui/std monomorphizations from the main binary → invalid pointer → crash.

Fix: .cargo/config.toml with --export-dynamic to export all symbols.

4. Workspace crate text not updating after hotpatch

This was the hardest bug. Three layers:

  1. subsecond::call inside workspace crate — ZST closure's call_it is not in the jump table diff → removed
  2. HotFn::current(workspace_fn).call() doesn't work — after the first tip-crate patch, App::ui runs from the patch .so. Inside the .so, workspace_crate_ui resolves through .so's GOT giving a .so address, but the jump table maps original_binary_addr → new_addr. So HotFn::call_as_ptr gets the .so address, doesn't find it in the jump table, and calls the old version.

Solution: OnceLock captures the original fn pointer before any patch. call_workspace_ui() does a direct lookup in subsecond::get_jump_table().map with this original address.

Before patch:  workspace_fn as ptr  →  0x437f020 (binary)   → found in jump table ✓
After patch:   workspace_fn as ptr  →  0x7f3a...fb90 (.so)  → NOT in jump table ✗
With OnceLock:  saved original ptr   →  0x437f020 (binary)   → found in jump table ✓

How Bevy compares

Bevy's #[hot] macro generates HotFn::current(fn).call() per system — but they don't support workspace crates at all ("only patches the tip crate"). Our OnceLock + jump table approach is the first working solution for workspace hotpatching.

Tested scenarios

All verified working on Linux (x86_64):

  • Tip crate changes (widgets, layouts, styles)
  • Workspace crate changes
  • Simultaneous tip + workspace changes
  • Crash recovery (panic → red error label → fix code → hotpatch picks up fix)
  • Adding new functions to workspace crate

Test plan

  • dx serve --hot-patch starts and builds successfully
  • Edit src/my_component.rs → changes appear in ~1s
  • Edit workspace_crate/src/lib.rs → changes appear in ~1s
  • Add panic!() to workspace crate → app shows red error, doesn't crash
  • Fix the panic → next hotpatch recovers

🤖 Generated with Claude Code

enomado and others added 6 commits March 29, 2026 01:59
…dency

- bump egui/eframe 0.32 → 0.34.1, subsecond 0.7.0-rc.0 → 0.7.4
- replace dioxus-devtools with lightweight connect_subsecond() reimplementation
  using only subsecond + dioxus-cli-config + tungstenite (avoids pulling full Dioxus stack)
- adapt to eframe 0.34 new App::ui() API (replaces App::update())
- remove "workspace not supported" comment — workspace hotpatching landed in subsecond 0.7.x

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…::call

Two fixes that enable workspace crate hotpatching on Linux:

1. Add .cargo/config.toml with `-Wl,--export-dynamic` for x86_64-linux.
   dx only exports the `main` symbol (--export-dynamic-symbol,main), so
   dlopen-ed patch .so cannot resolve egui/std monomorphizations from the
   host binary → SIGSEGV or "undefined symbol" on workspace crate patch.
   Full --export-dynamic makes all symbols available to patch .so via PLT.

2. Remove nested subsecond::call in subsecond_fn — keep single call point
   in App::ui. Nested HotFn causes stale pointer jumps after patch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HotFn::current(fn).call() doesn't work for workspace crates because
after the first tip-crate patch, App::ui runs from the patch .so,
and fn pointers resolve through .so's GOT — giving a .so address,
not the original binary address that the jump table maps from.

Solution: OnceLock captures the original fn pointer before any patch,
then call_workspace_ui() does a direct lookup in get_jump_table().map
to find the current patched address and calls it.

Also removes subsecond dependency from workspace_crate (not needed —
the tip crate handles all hotpatch mechanics).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers how subsecond works, why HotFn fails for workspace crates
(GOT/.so address mismatch), the OnceLock + jump table solution,
comparison with Bevy's approach, and Linux --export-dynamic fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Panic in workspace crate code no longer kills the app — it shows
a red error label in the UI. Fixing the code and saving applies
the next hotpatch without restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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