Skip to content

Commit 2d78ddd

Browse files
committed
test
1 parent 3f0fc2b commit 2d78ddd

7 files changed

Lines changed: 187 additions & 108 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/channel.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ impl Channel {
3333
Ok((Self::from(channel.port1()), channel.port2()))
3434
}
3535

36+
/// Create a Channel from pre-built components.
37+
///
38+
/// This is used internally when the onmessage callback needs custom routing
39+
/// (e.g., to split user messages from result messages on the same port).
40+
pub(crate) fn from_parts(
41+
messages: mpsc::UnboundedReceiver<JsValue>,
42+
port: MessagePort,
43+
) -> Self {
44+
Self {
45+
messages: Rc::new(RefCell::new(messages)),
46+
port,
47+
}
48+
}
49+
3650
/// Handle messages received by the port and forwards them into the message stream
3751
fn on_message_callback(
3852
sender: mpsc::UnboundedSender<JsValue>,

src/pool/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub use scheduler::Strategy;
77
use serde::{Deserialize, Serialize};
88

99
use wasm_bindgen_futures::JsFuture;
10-
use web_sys::{window, MessagePort};
10+
use web_sys::window;
1111

1212
use crate::{
1313
error::InitError,
@@ -215,21 +215,23 @@ impl WebWorkerPool {
215215
/// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
216216
/// is correctly exposed to the worker.
217217
///
218+
/// Returns a [`Channel`] for bidirectional communication and a future that resolves
219+
/// to the function's return value. See [`WebWorker::run_channel`] for details.
220+
///
218221
/// Example:
219222
/// ```ignore
220-
/// worker_pool().await.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
223+
/// let (channel, result) = worker_pool().await.run_channel(webworker_channel!(process_with_progress), &my_data).await;
221224
/// ```
222225
pub async fn run_channel<T, R>(
223226
&self,
224227
func: WebWorkerChannelFn<T, R>,
225228
arg: &T,
226-
port: MessagePort,
227-
) -> R
229+
) -> (crate::Channel, impl std::future::Future<Output = R> + '_)
228230
where
229231
T: Serialize + for<'de> Deserialize<'de>,
230232
R: Serialize + for<'de> Deserialize<'de>,
231233
{
232-
self.run_channel_internal(func, arg, port).await
234+
self.run_channel_internal(func, arg).await
233235
}
234236

235237
/// This function can outsource a task on a [`WebWorkerPool`] which has `Box<[u8]>` both as input and output.
@@ -271,15 +273,14 @@ impl WebWorkerPool {
271273
&self,
272274
func: WebWorkerChannelFn<T, R>,
273275
arg: &T,
274-
port: MessagePort,
275-
) -> R
276+
) -> (crate::Channel, impl std::future::Future<Output = R> + '_)
276277
where
277278
T: Serialize + for<'de> Deserialize<'de>,
278279
R: Serialize + for<'de> Deserialize<'de>,
279280
{
280281
let worker_id = self.scheduler.schedule(self);
281282
self.workers[worker_id]
282-
.run_channel_internal(func, arg, port)
283+
.run_channel_internal(func, arg)
283284
.await
284285
}
285286

src/webworker/js.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ console.debug('Initializing worker');
3737
3838
const worker_result = await fn(arg, event.ports[0]);
3939
40-
// For channel tasks, yield the event loop before posting the result.
41-
// Channel messages (sent via MessagePort.postMessage during execution)
42-
// and the task result (sent via self.postMessage) travel on independent
43-
// message paths. Without this yield, the main thread may receive the
44-
// result before all channel messages have been delivered.
45-
if (is_channel) {
46-
await new Promise(resolve => setTimeout(resolve, 0));
40+
if (is_channel && event.ports[0]) {
41+
// For channel tasks, send the result through the same MessagePort
42+
// used for channel messages. This guarantees FIFO ordering: all
43+
// channel messages sent during execution will be delivered before
44+
// the result, because a single MessagePort preserves message order.
45+
console.debug('Send channel result via port');
46+
event.ports[0].postMessage({ __wasmworker_result: worker_result });
47+
} else {
48+
// Send response back to be handled by callback in main thread.
49+
console.debug('Send worker result');
50+
self.postMessage({ id: id, response: worker_result });
4751
}
48-
49-
// Send response back to be handled by callback in main thread.
50-
console.debug('Send worker result');
51-
self.postMessage({ id: id, response: worker_result });
5252
});
5353
})();
5454
"#;
@@ -112,15 +112,15 @@ initHandler = async function(event) {
112112
113113
const worker_result = await fn(arg, event.ports[0]);
114114
115-
// For channel tasks, yield the event loop before posting the result.
116-
// See comment in WORKER_JS for rationale.
117-
if (is_channel) {
118-
await new Promise(resolve => setTimeout(resolve, 0));
115+
if (is_channel && event.ports[0]) {
116+
// See comment in WORKER_JS for rationale.
117+
console.debug('Send channel result via port');
118+
event.ports[0].postMessage({ __wasmworker_result: worker_result });
119+
} else {
120+
// Send response back to be handled by callback in main thread.
121+
console.debug('Send worker result');
122+
self.postMessage({ id: id, response: worker_result });
119123
}
120-
121-
// Send response back to be handled by callback in main thread.
122-
console.debug('Send worker result');
123-
self.postMessage({ id: id, response: worker_result });
124124
});
125125
}
126126
};

src/webworker/worker.rs

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919
convert::{from_bytes, to_bytes},
2020
error::{Full, InitError},
2121
func::{WebWorkerChannelFn, WebWorkerFn},
22+
Channel,
2223
};
2324

2425
/// An internal type for the callback.
@@ -238,23 +239,30 @@ impl WebWorker {
238239
/// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
239240
/// is correctly exposed to the worker.
240241
///
242+
/// Returns a [`Channel`] for bidirectional communication and a future that resolves
243+
/// to the function's return value. The channel and result share the same underlying
244+
/// `MessagePort`, guaranteeing that all channel messages sent during execution are
245+
/// delivered before the result.
246+
///
241247
/// If a task limit has been set, this function will yield until previous tasks have been finished.
242248
///
243249
/// Example:
244250
/// ```ignore
245-
/// worker.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
251+
/// let (channel, result) = worker.run_channel(webworker_channel!(process_with_progress), &my_data).await;
252+
/// let progress: Progress = channel.recv().await.unwrap();
253+
/// channel.send(&Continue { should_continue: true });
254+
/// let result = result.await;
246255
/// ```
247256
pub async fn run_channel<T, R>(
248257
&self,
249258
func: WebWorkerChannelFn<T, R>,
250259
arg: &T,
251-
port: MessagePort,
252-
) -> R
260+
) -> (Channel, impl std::future::Future<Output = R> + '_)
253261
where
254262
T: Serialize + for<'de> Deserialize<'de>,
255263
R: Serialize + for<'de> Deserialize<'de>,
256264
{
257-
self.run_channel_internal(func, arg, port).await
265+
self.run_channel_internal(func, arg).await
258266
}
259267

260268
/// This function differs from [`WebWorker::run`] by returning early if the given task limit is reached.
@@ -364,25 +372,103 @@ impl WebWorker {
364372
}
365373

366374
/// Internal function to schedule a channel task to the worker.
375+
///
376+
/// Creates a `MessageChannel` internally. The worker-side port is transferred
377+
/// to the worker; the main-side port is used for both user messages AND the
378+
/// task result (tagged with `__wasmworker_result`). Because everything travels
379+
/// through a single `MessagePort`, FIFO ordering is guaranteed: all channel
380+
/// messages sent during execution arrive before the result.
367381
pub(crate) async fn run_channel_internal<T, R>(
368382
&self,
369383
func: WebWorkerChannelFn<T, R>,
370384
arg: &T,
371-
port: MessagePort,
372-
) -> R
385+
) -> (Channel, impl std::future::Future<Output = R> + '_)
373386
where
374387
T: Serialize + for<'de> Deserialize<'de>,
375388
R: Serialize + for<'de> Deserialize<'de>,
376389
{
390+
use tokio::sync::mpsc;
391+
use web_sys::MessageChannel;
392+
377393
// Acquire permit if necessary.
378-
let _permit = if let Some(ref s) = self.task_limit {
394+
let permit = if let Some(ref s) = self.task_limit {
379395
Some(s.acquire().await.unwrap())
380396
} else {
381397
None
382398
};
383399

384-
// Convert arg and result.
385-
self.force_run(func.name, arg, true, Some(port)).await
400+
// Create a MessageChannel. The worker-side port is transferred to the
401+
// worker. The main-side port receives both user messages and the tagged
402+
// result, so we set up a dual-purpose onmessage callback.
403+
let msg_channel =
404+
MessageChannel::new().expect_throw("Could not create MessageChannel");
405+
let main_port = msg_channel.port1();
406+
let worker_port = msg_channel.port2();
407+
408+
let (result_tx, result_rx) = oneshot::channel::<JsValue>();
409+
let result_tx = Rc::new(RefCell::new(Some(result_tx)));
410+
let (user_tx, user_rx) = mpsc::unbounded_channel::<JsValue>();
411+
412+
// Callback that routes __wasmworker_result to result_tx, everything else
413+
// to user_tx (the Channel's message stream).
414+
let callback: Closure<dyn FnMut(MessageEvent)> = {
415+
let result_tx = Rc::clone(&result_tx);
416+
Closure::new(move |event: MessageEvent| {
417+
let data = event.data();
418+
// Check for the result tag (an Object with __wasmworker_result).
419+
// User messages are Uint8Array, so this check is unambiguous.
420+
if data.is_object() {
421+
if let Ok(result) = js_sys::Reflect::get(
422+
&data,
423+
&JsValue::from_str("__wasmworker_result"),
424+
) {
425+
if !result.is_undefined() {
426+
if let Some(tx) = result_tx.borrow_mut().take() {
427+
let _ = tx.send(result);
428+
}
429+
return;
430+
}
431+
}
432+
}
433+
let _ = user_tx.send(data);
434+
})
435+
};
436+
main_port.set_onmessage(Some(callback.as_ref().unchecked_ref()));
437+
callback.forget();
438+
439+
let channel = Channel::from_parts(user_rx, main_port);
440+
441+
// Serialize and send request (with port transfer, no open_tasks registration).
442+
let id = self.current_task.fetch_add(1, Ordering::Relaxed);
443+
let request = Request {
444+
id,
445+
func_name: func.name,
446+
is_channel: true,
447+
arg: to_bytes(arg),
448+
};
449+
450+
let transfer = Array::new();
451+
transfer.push(&worker_port);
452+
self.worker
453+
.post_message_with_transfer(
454+
&serde_wasm_bindgen::to_value(&request)
455+
.expect_throw("Could not serialize request"),
456+
&transfer,
457+
)
458+
.expect_throw("WebWorker gone");
459+
460+
// The result future awaits the tagged result from the port.
461+
let result_future = async move {
462+
let _permit = permit; // keep permit alive until result arrives
463+
let raw = result_rx
464+
.await
465+
.expect_throw("Channel result sender dropped");
466+
let array = js_sys::Uint8Array::new(&raw);
467+
let bytes = array.to_vec();
468+
from_bytes::<R>(&bytes)
469+
};
470+
471+
(channel, result_future)
386472
}
387473

388474
/// This function handles the communication with the worker

test/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ crate-type = ["cdylib"]
1717

1818
[dependencies]
1919
serde = { version = "1.0", features = ["derive"] }
20-
tokio = { version = "1.4", features = ["sync"] }
2120
wasm-bindgen = "0.2"
2221
wasm-bindgen-futures = "0.4"
23-
web-sys = { version = "0.3", features = ["MessagePort"] }
2422
wasmworker = { workspace = true }
2523
wasmworker-proc-macro = { workspace = true }

0 commit comments

Comments
 (0)