Skip to content

fix(core): resolve asset protocol requests off the webview thread (fix #7434) - #15836

Closed
PathGao wants to merge 3 commits into
tauri-apps:devfrom
PathGao:fix/asset-protocol-non-blocking
Closed

fix(core): resolve asset protocol requests off the webview thread (fix #7434)#15836
PathGao wants to merge 3 commits into
tauri-apps:devfrom
PathGao:fix/asset-protocol-non-blocking

Conversation

@PathGao

@PathGao PathGao commented Aug 6, 2026

Copy link
Copy Markdown

Summary

Closes #7434.

asset:// requests are served by a handler registered through wry's with_asynchronous_custom_protocol (crates/tauri-runtime-wry/src/lib.rs), an API whose point is that the handler may return immediately and respond later from another thread. The asset handler did not do that — it called get_response, which is synchronous std::fs I/O, inline and only then responded, so the whole read ran on the thread the webview invoked it on.

That is the thread running the event loop, so a read that blocks stalls every window driven by that event loop, not just the one that asked. This matches #7434, where an image on an unreachable SMB path freezes the entire app, and the reporter's own guess that "the rust-end was blocked".

This wraps the existing handler body in crate::async_runtime::spawn_blocking and changes nothing else. get_response is untouched, so the range/206 path, the HEAD path, the SafePathBuf and scope checks, the CORS header and the Android external-storage branch all keep their current behaviour — every one of them is reached through that single call, and both responder.respond arms moved with it, so there is no route that still responds from the calling thread.

Why spawn_blocking rather than spawn

#15220 did this for the sibling tauri:// protocol and used crate::async_runtime::spawn, because that get_response is async. This one is not: since #15117 (refactor(tauri): use blocking apis where it makes sense) the asset body is plain std::fs, which is what a blocking pool is for, and spawn would park an async worker on a blocking read. Happy to switch to spawn if you would rather the two protocols read the same way.

scope is Arc-backed so cloning it is a refcount bump, and window_origin is a short String. I kept it to those two clones rather than introducing an Arc<Context> as #15220 did, to hold the diff to the handler.

Testing

On macOS (aarch64):

  • cargo test -p tauri --all-features — 61 unit tests and 122 doc tests, all passing
  • cargo clippy -p tauri --all-targets --all-features — clean
  • cargo fmt --all -- --check — clean

I added one test, does_not_block_the_calling_thread. It uses a FIFO as a stand-in for a path that never answers — opening one for reading blocks until a writer appears — and asserts that the handler has returned while nothing has been read yet. I checked that it actually exercises the defect: with this change reverted and the test kept, it blocks indefinitely instead of passing. It is #[cfg(all(test, unix))] and calls the handler on a spawned thread, so a regression fails the test rather than hanging CI.

I separately checked, with a throwaway test I have not included, that the range path behaves identically through the new call: full GET, bytes=10-19, bytes=0-, bytes=-100, an unsatisfiable range, HEAD, a missing file and a scope denial all produce the same status, the same content-range / content-length / accept-ranges headers and the same bodies as before. I left it out to keep the diff to the fix; glad to add it if you want the coverage.

What I could not verify

I have no SMB share or other unreachable network path available, so I have not reproduced #7434 itself, and I have not confirmed the fix against the reporter's scenario. The case that this fixes it rests on the code and on the FIFO test, not on the original repro.

One behaviour change worth flagging

On Android, wry blocks shouldInterceptRequest waiting on the responder with MAIN_PIPE_TIMEOUT * 3 — 30s, src/android/mod.rs in wry 0.56. Today the asset handler responds inline, so that deadline is never reached; after this change a read taking longer than 30s would time out and the request would fall through to Android's network stack instead of eventually succeeding. #15220 already put tauri:// in the same position. That seemed the right trade against freezing the app for those same 30s, but I am happy to handle it differently if you disagree.

@PathGao
PathGao requested a review from a team as a code owner August 6, 2026 15:33
@Legend-Master Legend-Master added the ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy label Aug 6, 2026
@PathGao PathGao closed this by deleting the head repository Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] load image file by asset protocol from smb will hang the entire application.

2 participants