Skip to content
This repository was archived by the owner on Jun 10, 2024. It is now read-only.

Commit 3b92994

Browse files
committed
Non-blocking web scheduler loop
Instead of having to make a scheduler loop for each potential `wasm_bindgen` function, the loop is now started with `lumen_web::start()`, which should be called from the `wasm_bindgen(start)` function. `lumen_web::start()` runs creates a self-rescheduling closure based on https://github.com/rustwasm/wasm-bindgen/blob/603d5742eeca2a7a978f13614de9282229d1835e/examples/request-animation-frame/src/lib.rs from the Rust WASM Guide. In the closure, `run_for_milliseconds(MILLISECONDS_PER_FRAME)` is run. `run_for_milliseconds` calls `Scheduler::run_once` in a loop until the timeout expires or `run_once` returns `false`, which means all processes are waiting, so it is probably a good time to yield back to the JS event loop. This leaves the tab spinning at 4-5% CPU with no calls in flight. We could likely infer that more resting is possible by checking that the timers are also empty and then doing something to wake up the loop when a new process is spawned by a JS call. All `wasm_bindgen` functions now return `JsPromise`. The promise is created by by `lumen_web::wait::with_return_0::spawn`, which setups up a `with_return_0::frame()` and then adds on the frame for the actual Code to be called from JS. When the Code returns the value to `with_return_0::code`, it resolves the Promise. Verified that the DOM nodes are actively updated throughout the run of `Chain.log_to_dom(8192)`. Fixes #205
1 parent 1d7f0ac commit 3b92994

File tree

9 files changed

+392
-360
lines changed

9 files changed

+392
-360
lines changed

Cargo.lock

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

examples/spawn-chain/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ time_web_sys = ["parking_lot_core/time_web_sys", "lumen_runtime/time_web_sys"]
1818
# code size when deploying.
1919
console_error_panic_hook = { version = "0.1.1", optional = true }
2020

21+
js-sys = "0.3.25"
2122
liblumen_alloc = { path = "../../liblumen_alloc" }
2223
lumen_runtime = { path = "../../lumen_runtime" }
2324
lumen_web = { path = "../../lumen_web" }
@@ -42,4 +43,6 @@ features = ['console']
4243

4344
[dev-dependencies]
4445
time-test = "0.2.1"
46+
futures = "0.1.28"
47+
wasm-bindgen-futures = "0.3.26"
4548
wasm-bindgen-test = "0.2"

examples/spawn-chain/src/lib.rs

Lines changed: 20 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,17 @@ mod apply_3;
77
mod elixir;
88
mod start;
99

10-
use std::convert::TryInto;
11-
12-
use liblumen_alloc::erts::exception;
1310
use liblumen_alloc::erts::process::code::stack::frame::Placement;
14-
use liblumen_alloc::erts::process::{heap, next_heap_size, Status};
11+
use liblumen_alloc::erts::process::{heap, next_heap_size};
1512

1613
use lumen_runtime::scheduler::Scheduler;
17-
use lumen_runtime::system;
1814

1915
use lumen_web::wait;
2016

2117
use wasm_bindgen::prelude::*;
2218

2319
use crate::elixir::chain::{console_1, dom_1};
2420
use crate::start::*;
25-
use liblumen_alloc::erts::term::atom_unchecked;
2621

2722
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
2823
// allocator.
@@ -34,15 +29,16 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
3429
pub fn start() {
3530
set_panic_hook();
3631
set_apply_fn();
32+
lumen_web::start();
3733
}
3834

3935
#[wasm_bindgen]
40-
pub fn log_to_console(count: usize) -> usize {
36+
pub fn log_to_console(count: usize) -> js_sys::Promise {
4137
run(count, Output::Console)
4238
}
4339

4440
#[wasm_bindgen]
45-
pub fn log_to_dom(count: usize) -> usize {
41+
pub fn log_to_dom(count: usize) -> js_sys::Promise {
4642
run(count, Output::Dom)
4743
}
4844

@@ -51,160 +47,34 @@ enum Output {
5147
Dom,
5248
}
5349

54-
fn run(count: usize, output: Output) -> usize {
50+
fn run(count: usize, output: Output) -> js_sys::Promise {
5551
let arc_scheduler = Scheduler::current();
5652
// Don't register, so that tests can run concurrently
5753
let parent_arc_process = arc_scheduler.spawn_init(0).unwrap();
5854

59-
// if not enough memory here, resize `spawn_init` heap
60-
let count_term = parent_arc_process.integer(count).unwrap();
61-
6255
let heap_size = next_heap_size(79 + count * 5);
6356
// if this fails the entire tab is out-of-memory
6457
let heap = heap(heap_size).unwrap();
6558

66-
let run_arc_process = wait::with_return_0::spawn(
59+
wait::with_return_0::spawn(
6760
&parent_arc_process,
6861
heap,
69-
heap_size
70-
)
71-
// if this fails use a bigger sized heap
72-
.unwrap();
73-
74-
match output {
75-
Output::Console => {
76-
// if this fails use a bigger sized heap
77-
console_1::place_frame_with_arguments(&run_arc_process, Placement::Push, count_term)
78-
.unwrap()
79-
}
80-
Output::Dom => {
81-
// if this fails use a bigger sized heap
82-
dom_1::place_frame_with_arguments(&run_arc_process, Placement::Push, count_term)
83-
.unwrap()
84-
}
85-
};
86-
87-
let mut option_return_usize: Option<usize> = None;
88-
89-
loop {
90-
let ran = Scheduler::current().run_through(&run_arc_process);
91-
92-
let waiting = match *run_arc_process.status.read() {
93-
Status::Exiting(ref exception) => match exception {
94-
exception::runtime::Exception {
95-
class: exception::runtime::Class::Exit,
96-
reason,
97-
..
98-
} => {
99-
if *reason != atom_unchecked("normal") {
100-
panic!("ProcessControlBlock exited: {:?}", reason);
101-
} else {
102-
break;
103-
}
62+
heap_size,
63+
|child_process| {
64+
let count_term = child_process.integer(count)?;
65+
66+
match output {
67+
Output::Console => {
68+
69+
// if this fails use a bigger sized heap
70+
console_1::place_frame_with_arguments(child_process, Placement::Push, count_term)
10471
}
105-
_ => {
106-
panic!(
107-
"ProcessControlBlock exception: {:?}\n{:?}",
108-
exception,
109-
run_arc_process.stacktrace()
110-
);
72+
Output::Dom => {
73+
// if this fails use a bigger sized heap
74+
dom_1::place_frame_with_arguments(child_process, Placement::Push, count_term)
11175
}
112-
},
113-
Status::Waiting => true,
114-
Status::Runnable => false,
115-
Status::Running => {
116-
system::io::puts(&format!(
117-
"RUNNING Run queues len = {:?}",
118-
Scheduler::current().run_queues_len()
119-
));
120-
121-
false
12276
}
123-
};
124-
125-
// separate so we don't hold read lock on status as it may need to be written
126-
if waiting {
127-
if ran {
128-
system::io::puts(&format!(
129-
"WAITING Run queues len = {:?}",
130-
Scheduler::current().run_queues_len()
131-
));
132-
} else {
133-
use wait::with_return_0::Error::*;
134-
135-
match wait::with_return_0::pop_return(&run_arc_process) {
136-
Ok(popped_return) => {
137-
option_return_usize = Some(popped_return.try_into().unwrap());
138-
wait::with_return_0::stop(&run_arc_process);
139-
},
140-
Err(NoModuleFunctionArity) => panic!("{:?} doesn't have a current module function arity", run_arc_process),
141-
Err(WrongModuleFunctionArity(current_module_function_arity)) => panic!(
142-
"{:?} is not waiting with a return and instead did not run while waiting in {}. Deadlock likely in {:#?}",
143-
run_arc_process,
144-
current_module_function_arity,
145-
Scheduler::current()
146-
),
147-
Err(NoReturn) => panic!("{:?} is waiting, but nothing was returned to it. Bug likely in {:#?}", run_arc_process, Scheduler::current()),
148-
Err(TooManyReturns(returns)) => panic!("{:?} got multiple returns: {:?}. Stack is not being properly managed.", run_arc_process, returns)
149-
}
150-
}
151-
}
152-
}
153-
154-
option_return_usize.unwrap()
155-
}
156-
157-
#[cfg(test)]
158-
mod tests {
159-
use super::*;
160-
161-
use std::sync::Once;
162-
163-
mod log_to_console {
164-
use super::*;
165-
166-
#[test]
167-
fn with_1() {
168-
start_once();
169-
assert_eq!(log_to_console(1), 1);
170-
}
171-
172-
#[test]
173-
fn with_2() {
174-
start_once();
175-
assert_eq!(log_to_console(2), 2);
176-
}
177-
178-
#[test]
179-
fn with_4() {
180-
start_once();
181-
assert_eq!(log_to_console(4), 4);
182-
}
183-
184-
#[test]
185-
fn with_8() {
186-
start_once();
187-
assert_eq!(log_to_console(8), 8);
188-
}
189-
190-
#[test]
191-
fn with_16() {
192-
start_once();
193-
assert_eq!(log_to_console(16), 16);
194-
}
195-
196-
#[test]
197-
fn with_32() {
198-
start_once();
199-
assert_eq!(log_to_console(32), 32);
200-
}
201-
}
202-
203-
static START: Once = Once::new();
204-
205-
fn start_once() {
206-
START.call_once(|| {
207-
start();
20877
})
209-
}
78+
// if this fails use a bigger sized heap
79+
.unwrap()
21080
}

examples/spawn-chain/tests/web.rs

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ extern crate wasm_bindgen_test;
55

66
use std::sync::Once;
77

8+
use futures::future::Future;
9+
10+
use wasm_bindgen::JsValue;
11+
12+
use wasm_bindgen_futures::JsFuture;
13+
814
use wasm_bindgen_test::*;
915

1016
use spawn_chain::start;
@@ -16,40 +22,38 @@ mod log_to_console {
1622

1723
use spawn_chain::log_to_console;
1824

19-
#[wasm_bindgen_test]
20-
fn with_1() {
21-
start_once();
22-
assert_eq!(log_to_console(1), 1);
25+
#[wasm_bindgen_test(async)]
26+
fn with_1() -> impl Future<Item = (), Error = JsValue> {
27+
eq_in_the_future(1)
2328
}
2429

25-
#[wasm_bindgen_test]
26-
fn with_2() {
27-
start_once();
28-
assert_eq!(log_to_console(2), 2);
30+
#[wasm_bindgen_test(async)]
31+
fn with_2() -> impl Future<Item = (), Error = JsValue> {
32+
eq_in_the_future(2)
2933
}
3034

31-
#[wasm_bindgen_test]
32-
fn with_4() {
33-
start_once();
34-
assert_eq!(log_to_console(4), 4);
35+
#[wasm_bindgen_test(async)]
36+
fn with_4() -> impl Future<Item = (), Error = JsValue> {
37+
eq_in_the_future(4)
3538
}
3639

37-
#[wasm_bindgen_test]
38-
fn with_8() {
39-
start_once();
40-
assert_eq!(log_to_console(8), 8);
40+
#[wasm_bindgen_test(async)]
41+
fn with_8() -> impl Future<Item = (), Error = JsValue> {
42+
eq_in_the_future(8)
4143
}
4244

43-
#[wasm_bindgen_test]
44-
fn with_16() {
45-
start_once();
46-
assert_eq!(log_to_console(16), 16);
45+
#[wasm_bindgen_test(async)]
46+
fn with_16() -> impl Future<Item = (), Error = JsValue> {
47+
eq_in_the_future(16)
4748
}
4849

49-
#[wasm_bindgen_test]
50-
fn with_32() {
51-
start_once();
52-
assert_eq!(log_to_console(32), 32);
50+
#[wasm_bindgen_test(async)]
51+
fn with_32() -> impl Future<Item = (), Error = JsValue> {
52+
eq_in_the_future(32)
53+
}
54+
55+
fn eq_in_the_future(n: usize) -> impl Future<Item = (), Error = JsValue> {
56+
super::eq_in_the_future(log_to_console, n)
5357
}
5458
}
5559

@@ -58,45 +62,60 @@ mod log_to_dom {
5862

5963
use spawn_chain::log_to_dom;
6064

61-
#[wasm_bindgen_test]
62-
fn with_1() {
63-
start_once();
64-
assert_eq!(log_to_dom(1), 1);
65+
#[wasm_bindgen_test(async)]
66+
fn with_1() -> impl Future<Item = (), Error = JsValue> {
67+
eq_in_the_future(1)
68+
}
69+
70+
#[wasm_bindgen_test(async)]
71+
fn with_2() -> impl Future<Item = (), Error = JsValue> {
72+
eq_in_the_future(2)
6573
}
6674

67-
#[wasm_bindgen_test]
68-
fn with_2() {
69-
start_once();
70-
assert_eq!(log_to_dom(2), 2);
75+
#[wasm_bindgen_test(async)]
76+
fn with_4() -> impl Future<Item = (), Error = JsValue> {
77+
eq_in_the_future(4)
7178
}
7279

73-
#[wasm_bindgen_test]
74-
fn with_4() {
75-
start_once();
76-
assert_eq!(log_to_dom(4), 4);
80+
#[wasm_bindgen_test(async)]
81+
fn with_8() -> impl Future<Item = (), Error = JsValue> {
82+
eq_in_the_future(8)
7783
}
7884

79-
#[wasm_bindgen_test]
80-
fn with_8() {
81-
start_once();
82-
assert_eq!(log_to_dom(8), 8);
85+
#[wasm_bindgen_test(async)]
86+
fn with_16() -> impl Future<Item = (), Error = JsValue> {
87+
eq_in_the_future(16)
8388
}
8489

85-
#[wasm_bindgen_test]
86-
fn with_16() {
87-
start_once();
88-
assert_eq!(log_to_dom(16), 16);
90+
#[wasm_bindgen_test(async)]
91+
fn with_32() -> impl Future<Item = (), Error = JsValue> {
92+
eq_in_the_future(32)
8993
}
9094

91-
#[wasm_bindgen_test]
92-
fn with_32() {
93-
start_once();
94-
assert_eq!(log_to_dom(32), 32);
95+
fn eq_in_the_future(n: usize) -> impl Future<Item = (), Error = JsValue> {
96+
super::eq_in_the_future(log_to_dom, n)
9597
}
9698
}
9799

98100
static START: Once = Once::new();
99101

102+
fn eq_in_the_future(
103+
f: fn(usize) -> js_sys::Promise,
104+
n: usize,
105+
) -> impl Future<Item = (), Error = JsValue> {
106+
start_once();
107+
108+
let promise = f(n);
109+
110+
JsFuture::from(promise)
111+
.map(move |resolved| {
112+
let n_js_value: JsValue = (n as i32).into();
113+
114+
assert_eq!(resolved, n_js_value)
115+
})
116+
.map_err(|_| unreachable!())
117+
}
118+
100119
fn start_once() {
101120
START.call_once(|| {
102121
start();

0 commit comments

Comments
 (0)