wasm-bindgen
wasm-bindgen copied to clipboard
Async Rust function blocks main thread
Describe the Bug
My async Rust function, which is exported to JS using wasm_bindgen, blocks the main thread when called.
Steps to Reproduce
This is how the async function gets called:
JsNote.getNotesFromVault(vault, metadataCache)
.then((jsNotes: JsNote[]) => wasm.find(this, jsNotes as Note[], onLinkMatchingProgress)) // <- the async function
.then(...)
This is its definition:
#[wasm_bindgen]
pub async fn find (context: JsValue, notes: NoteArray, callback: js_sys::Function) -> Result<JsValue, JsValue> {
let arr = notes.unchecked_into::<Array>();
let notes: Vec<Note> = arr.iter()
.filter_map(|note: JsValue| Note::try_from(note).ok())
.collect();
let res: Array = Array::new();
for note in ¬es {
let args = js_sys::Array::new();
let note_scanned_event = NoteScannedEvent::new(note);
let js: JsValue = note_scanned_event.into();
args.push(&js);
callback.apply(&context, &args).unwrap();
// this function is another async one and takes about 2 seconds to complete:
let link_matches_result_future = link_matcher::get_link_matches(note, ¬es);
let link_matches_result = link_matches_result_future.await;
if let Ok(r) = link_matches_result {
res.push(&r);
}
};
Ok(res.into())
}
Expected Behavior
I expect the async code to not block the main thread.
Actual Behavior
The whole UI (electron) freezes, as if the code was not async.
Additional Context
Part of an electron project.
There is wasm-bindgen-futures crate for async behaviour.
There is
wasm-bindgen-futurescrate for async behaviour.
AFAIK the wasm_bindgen_futures::spawn_local function only supports Future<Output = ()>. How could this function be used to return a value?
There is
wasm-bindgen-futurescrate for async behaviour.
That isn't the problem, exporting an async function uses wasm-bindgen-futures internally.
What does get_link_matches actually do? If it's a blocking calculation, making it async doesn't move it off the main thread; you'd need to use a Worker for that. Otherwise, I've got no idea what might be causing this.
There is
wasm-bindgen-futurescrate for async behaviour.That isn't the problem, exporting an async function uses
wasm-bindgen-futuresinternally.What does
get_link_matchesactually do? If it's a blocking calculation, making itasyncdoesn't move it off the main thread; you'd need to use aWorkerfor that. Otherwise, I've got no idea what might be causing this.
get_link_matches is a function that does some regex matching, which takes like 1-2 seconds. The function is defined as async, this is the code:
pub async fn get_link_matches(note_to_check: &Note, target_note_candidates: &Vec<Note>) -> Result<JsValue, JsValue> {
let link_matches: Vec<LinkMatch> =
target_note_candidates
.iter()
.filter_map(|target_note: &Note| {
if !&target_note.title().eq(¬e_to_check.title()) {
let link_matcher_result = LinkMatcherResult::new(
¬e_to_check,
target_note
);
let link_matches: Vec<LinkMatch> = link_matcher_result.into();
return Some(link_matches);
}
None
})
.flatten()
.fold(Vec::new(), |mut merged_link_matches, mut link_match| {
let index = merged_link_matches.iter()
.position(|m: &LinkMatch| m.position().is_equal_to(&link_match.position()));
if let Some(index) = index {
// merge it into the existing match, if the position is the same
merged_link_matches[index].merge_link_match_target_candidates(link_match);
} else {
// otherwise push a new match
merged_link_matches.push(link_match);
}
merged_link_matches
});
if !link_matches.is_empty() {
let js_note_matching_result: JsValue = NoteMatchingResult::new(
note_to_check.clone(),
link_matches
).into();
Ok(js_note_matching_result)
} else {
Err(js_sys::Error::new("idk").into())
}
}
Yep, that's the problem - making a function async does nothing if you aren't actually await-ing anything. If you don't want it to block the main thread, you have to use a Worker to do that in the background.
See https://github.com/mdn/dom-examples/blob/master/web-workers/simple-web-worker/main.js for a basic example of how they work; you'd have to set up a system to resolve the right Promise when the message with the result is received. That requires making a completely separate WebAssembly instance on the background thread though, so it won't work if you have to share any state with the main thread.
If you need to share stuff with the main thread, you could use multithreaded WebAssembly (example). It requires nightly Rust and a bit of extra setup to use though.
AFAIK the wasm_bindgen_futures::spawn_local function only supports
Future<Output = ()>. How could this function be used to return a value?
@AlexW00 Did you ever figure out how to get a result from a future on wasm?