Skip to content

Fix hotpatch cascade replaying workspace crates not built for the active target - #5746

Closed
humdrum00001010 wants to merge 2 commits into
DioxusLabs:mainfrom
humdrum00001010:fix/hotpatch-cascade-target-filter
Closed

Fix hotpatch cascade replaying workspace crates not built for the active target#5746
humdrum00001010 wants to merge 2 commits into
DioxusLabs:mainfrom
humdrum00001010:fix/hotpatch-cascade-target-filter

Conversation

@humdrum00001010

@humdrum00001010 humdrum00001010 commented Aug 7, 2026

Copy link
Copy Markdown

patch_rebuild's cascade decides which workspace crates a hot patch must carry. It walks workspace_dependents_of, which reads the whole-workspace Cargo metadata graph — and that graph is target-agnostic. So the cascade could pick up a crate the active build never compiled: a native-only sibling, or a dependency behind [target.'cfg(not(target_arch = "wasm32"))'.dependencies] while serving wasm. No rustc invocation was captured for such a crate, so compile_workspace_hotpatch hard-errored with Missing rustc args for replay: '<crate>', and hot-patching stayed broken until dx serve was restarted.

First commit excludes those crates on both routes into the replay set — the crates the user edited and the dependents found by walking — and prunes the walk through them, since anything reachable only via a crate this target never compiled doesn't consume the change either. A dependent that also has a compiled path back to the edited crate is still reached along that path, so excluding one route never drops a crate that genuinely needs the patch. The walk is extracted as crates_to_replay and covered by eight tests over a fake dependents graph, including the "cascade is unchanged when every crate is built" baseline.

Second commit stops the wasted work this exposes: editing an excluded crate still ran a full patch cycle — thin rebuild, relink, jump table, client reload — with an empty replay list, because the tip is always rebuilt. It now returns before starting the build, while leaving behaviour untouched when a changed file maps to no workspace crate at all.

Verified on a workspace with a cfg-gated native-only dependency (wasm_appshared_lib, plus native_ffi depending on shared_lib and reachable from wasm_app only for non-wasm targets):

edit before after
native_ffi (never built for wasm32) Missing rustc args for replay: 'native_ffi', builder stuck skipped, no patch cycle
shared_lib (shared, in the graph) patch applies patch applies, identical cascade

cargo test --workspace --tests passes (208 suites), as do cargo fmt and cargo clippy --all-targets -- -D warnings.

Same root cause as #5596, which was closed as a duplicate of #5540 — see the repro and analysis posted there.

Closes #5540

@nicoburns
nicoburns requested a review from jkelleyrtp August 7, 2026 11:03
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

Reviewed the diff against the surrounding cascade/replay code.

The fix looks correct for the described bug. workspace_dependents_of() returns underscore-normalized package names and workspace_rustc_args keys are {crate}.lib/{crate}.bin in the same convention, so contains_crate matches the exact lookup workspace_hotpatch_replay_args() does later — an uncaptured dependent can no longer enter modified_crates via the cascade. Pruning the BFS through an uncaptured crate is also right: if an intermediate dependent wasn't compiled for this target, dependents reachable only through it don't consume the change on this target either (and any captured dependent with a direct/captured path to the changed crate is still found).

One gap: the seeds aren't filtered. In patch_rebuild, changed_crates themselves are inserted into modified_crates unconditionally (self.modified_crates.insert(c.clone()) runs before the new filter, which only guards dependents). If a user edits a file inside the uncaptured crate itself (e.g. touches native_ffi/src/lib.rs directly while serving the wasm app), file_to_workspace_crate maps it to native_ffi, it lands in modified_crates, and compile_workspace_hotpatch hits the same Missing rustc args for replay: 'native_ffi' hard error — so "a crate can no longer reach link.rs in a state that lookup would reject" isn't quite true yet. Suggest applying the same check to the seed, e.g.:

while let Some(c) = to_visit.pop() {
    if !visited.insert(c.clone()) {
        continue;
    }
    if c != tip_crate_name && !artifacts.workspace_rustc.contains_crate(&c) {
        continue; // edited crate isn't part of this build's target graph
    }
    self.modified_crates.insert(c.clone());
    ...

(keeping the tip_crate_name exemption, since the tip is tracked by package name which can differ from its captured .bin target name — see workspace_hotpatch_replay_order's comment).

Minor/non-blocking:

  • modified_crates is cumulative across patches, so anything that slipped in before this fix persists until the next fat rebuild; not an issue for new sessions, just worth knowing.
  • The nearby comment referencing compile_workspace_deps() staying "in sync automatically" is stale (that function no longer exists) — pre-existing, not this PR's problem.

Unit tests are reasonable and CI is green. With the seed filter added this looks good to merge.

@nicoburns

Copy link
Copy Markdown
Member

Ack. Didn't mean to send you automated review feedback. Never-the-less, I'd be interested in whether the review comments make sense to you.

@humdrum00001010

humdrum00001010 commented Aug 21, 2026

Copy link
Copy Markdown
Author

Scope note: the indexing is ~2ms of a ~250ms patch here, under 1% — it only shows past ~400 members. The skip saves a reload on an uncommon edit. Only the replay fix changes whether --hot-patch works at all, and just for workspaces with a crate outside the served target.

@humdrum00001010

humdrum00001010 commented Aug 21, 2026

Copy link
Copy Markdown
Author

Seed case is patched. The crates you edit and the dependents found while walking now pass one exclusion check, so neither route reaches replay uncaptured. Covered by tests against a real cargo workspace with a cfg-gated native-only dep.

@humdrum00001010
humdrum00001010 force-pushed the fix/hotpatch-cascade-target-filter branch 5 times, most recently from 15ceadf to b9f0498 Compare August 21, 2026 13:48
…play

Workspace dependency graphs are target-agnostic, so the cascade that decides
which crates a patch carries could pick up one the active build never compiled -
a native-only sibling, or a dependency behind
`[target.'cfg(...)'.dependencies]` while serving wasm. Nothing was captured to
replay for it, so the patch failed with
`Missing rustc args for replay: '<crate>'` until `dx serve` was restarted.

Exclude such crates on both routes into the set and prune the walk through them;
a dependent with a compiled path back to the edited crate is still reached along
that path.

Reverse-dependency lookup moves to `Workspace`, which owns the graph, and
indexes members once at load rather than rescanning and re-normalising every
member per query - the cascade asks once per visited crate, so a patch made
~2xV of those passes. On this workspace that walk drops from ~2.1ms to ~30us.
Editing an excluded crate still ran a full patch cycle - rebuild, relink, jump
table, client reload - with an empty replay list, since the tip is always
rebuilt. The edit cannot reach the running binary, so return before building.

Files that map to no workspace crate say nothing about relevance and keep going
through the normal patch path.
@humdrum00001010
humdrum00001010 force-pushed the fix/hotpatch-cascade-target-filter branch from b9f0498 to b31d74b Compare August 21, 2026 14:00
@humdrum00001010

humdrum00001010 commented Aug 25, 2026

Copy link
Copy Markdown
Author

@nicoburns The seed case wasn't that performance critical from my reproduction, but I patched. could you review this?

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.

dx serve --hotpatch tries to replay uncaptured workspace crates

2 participants