如何用 Subsecond 为 Dioxus 应用启用 Rust 热补丁
【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus
Dioxus 0.7 内置了 Subsecond 热补丁引擎:在应用运行期间直接替换 Rust 函数代码,让你在同一个会话里同时迭代前端和后端,而不必重启进程。本文面向已经能跑通dx serve的 Dioxus 开发者,说明如何把 Subsecond 接入一个现有 Dioxus 应用、以dx serve --hotpatch启动,并验证热补丁是否生效。适用前提是 Dioxus 0.7 版本——Subsecond 目前仍是实验功能,需要显式的--hotpatch参数才会启用。
准备工作
- 安装 Dioxus CLI(文档推荐
cargo binstall):
cargo binstall dioxus-cli确认运行平台在支持范围内(见 subsecond 文档):
- Linux(x86_64, aarch64)、macOS(x86_64, aarch64)、Windows(x86_64, arm64)
- Android(arm64-v8a, armeabi-v7a)
- WebAssembly(wasm32)
- iOS 仅支持模拟器;真机因 code-signing 要求目前不支持
注意 Subsecond 只在
debug_assertions开启时生效(即开发构建)。生产构建中subsecond::call会直接透传执行,无性能开销,因此可以放心地在代码里保留热补丁接入点。
在应用中标记热补丁点
Subsecond 采用显式集成点而非隐式函数劫持:只有放在"干净"位置的热补丁点才能安全生效。对于 Dioxus 应用,热补丁已经与组件和 server functions 直接集成,dioxus::launch(app)初始化时 devtools 会自动连接,通常不需要额外改动:
fn main() { dioxus::launch(app); // Devtools automatically connects during init }如果你的应用包含 Dioxus 之外的长期运行逻辑(游戏循环、请求循环等),在调用链的"干净入口"用subsecond::call包裹。以下代码取自 0.7 发布说明中的示例:
pub fn launch() { loop { std::thread::sleep(std::time::Duration::from_secs(1)); subsecond::call(|| tick()); } } fn tick() { println!("edit me to see the loop in action!!!!!!!!! "); }修改tick的内容后,循环里的打印会立即使用新版本,而launch外层代码不受影响。热补丁点可以嵌套:call的try_call会优先查全局 jump table,找到新版本就调用新版本;如果变化发生在当前补丁点上方,会发出HotFnPanic安全地向调用栈中更外层的热补丁点回溯并重试,从而把状态丢失限制在最小范围(见 Subsecond README)。
对于非 Dioxus 的 Rust 应用,官方提供的接入方式是通过dioxus-devtoolscrate 建立 devtools 连接并在循环中调用call(仓库中的 cross-tls-test 示例 即如此使用):
fn main() { dioxus_devtools::connect_subsecond(); loop { dioxus_devtools::subsecond::call(|| { handle_request() }); } }异步应用(tokio)则用dioxus_devtools::serve_subsecond_with_args把应用主体传入,它会捕获补丁消息、应用 jump table、丢弃当前 future 并用热函数创建新 future 后继续执行:
#[tokio::main] async fn main() { dioxus_devtools::serve_subsecond_with_args( state, |state| async { app_main(state).await } ).await; }以上两段引自 Hot-Reload 架构文档 的 Integration Flow 章节。
用 dx serve --hotpatch 启动并触发补丁
在 Dioxus 项目根目录执行:
dx serve --hotpatchdx serve的参数与cargo run相同;Subsecond 仍被标记为实验特性,所以 0.7 版本必须带上--hotpatch(serve 参数定义了--hotpatch别名,见 serve 参数定义)。
保存代码后,补丁按如下流程下发(见 架构文档 的 Patch Application Flow):
- 首次是全量构建(fat build),生成符号表并创建
HotpatchModuleCache; - 文件变更后 CLI 发起 thin build——ThinLink 只编译被修改的函数,产出最小的补丁 dylib;
- 补丁经 devtools WebSocket 发送(应用连接
ws://localhost:3000/_dioxus,查询参数包含build_id、pid、aslr_reference); - 应用端
subsecond::call站点通过apply_patch()用libloading::Library加载补丁库,以main符号为锚点计算 ASLR 偏移,然后更新全局APP_JUMP_TABLE; - 原始可执行文件不被修改,只有 jump table 从旧地址指向新编译版本。
验证热补丁是否生效
- 行为验证:按上面的示例编辑被
subsecond::call包裹的函数(例如tick),保存文件,观察运行中的进程输出/行为切换为新版本,且进程无需重启。发布说明给出的判断标准就是"edit me to see the loop in action"。 - 协议层观察:devtools WebSocket 上的消息类型可以反映补丁状态:
HotPatchStart表示二进制补丁开始应用,FullReloadFailed表示构建失败,FullReloadCommand表示需要整页重载(Web 端)。 - 补丁未下发的信号:CLI 侧如果客户端未连接、没有 ASLR reference,会打印
Ignoring hotpatch since there is no ASLR reference. Is the client connected?(见 builder.rs)。出现该日志时先确认应用进程已连上 devserver。
已知限制与边界
以下内容直接影响你能改哪些代码,改之前先对照(来源:subsecond 文档 与 架构文档):
- workspace 只补丁 tip crate:只有
main.rs所在 crate 会被补丁,其他 crate 的修改会被忽略;main.rs反向依赖lib.rs的布局也不会产生合理补丁。 - struct 布局/对齐变化不支持:生成代码假定特定布局,布局改变会导致崩溃。Dioxus 的应对方式是丢弃旧状态并从头重建,所以正常使用不会看到 segfault,但热补丁会丢失运行时状态。
- 静态初始化器变化不会被观察;运行时新增的 global 其析构函数永远不会执行(这是有意设计,Dioxus、Tokio 等依赖持久全局 runtime)。
- tip crate 中的 thread-local 在新补丁后看似重置为初始值(补丁库的 TLS 段独立于主可执行文件,Subsecond 不重绑 TLS 访问);依赖 crate 中的 thread-local 工作正常。
- 指针版本化未实现:
ptr_address总是返回最新函数地址,即使函数内容没变,所有函数指针都被视为"新"。 - WASM 平台是"limited module reloading",能力低于原生平台。
可选:手动生成补丁(dx hotpatch)
dx serve --hotpatch之外,CLI 还提供手动补丁入口 dx hotpatch:先用 fat build 把结构化产物写到文件,再把产物从 stdin 传入并给出运行中进程的 ASLR reference:
dx build --fat-binary >> output.json cat output.json | dx hotpatch --aslr-reference 0x12345678--aslr-reference是运行中应用上报的main地址,用于生成正确的补丁偏移;加--patch-server可以改为补丁服务端而非客户端(默认补丁 client)。该入口面向需要自行编排补丁流水线的场景,常规开发用dx serve --hotpatch即可。
【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考