Skip to content

Commit 90bb588

Browse files
pchickeySilverMira
andauthored
Allow non-pollable woken futures to make progress (#71)
* Add a new test that uses futures that do not pend on wasi pollables This test demonstrates behavior for a future which does not pend on a pollable, but does wake the root task when pending. As long as the root task has had wake() called, the runtime should keep polling it. This bug was reported in #69 and this test was derived from one written by @SilverMira in https://github.com/SilverMira/wstd/tree/allow_independent_futures Co-Authored-By: SilverMira <[email protected]> * track root task's awake status, and skip blocking on pollables if awake * CI: run cargo test with --nocapture so i can see the assertion that failed * fix windows CI * comment! --------- Co-authored-by: SilverMira <[email protected]>
1 parent 2218692 commit 90bb588

File tree

3 files changed

+94
-31
lines changed

3 files changed

+94
-31
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ jobs:
4242
uses: actions-rs/cargo@v1
4343
with:
4444
command: test
45-
args: -p wstd --target wasm32-wasip2
45+
args: -p wstd --target wasm32-wasip2 -- --nocapture
4646

4747
- name: example tests
4848
uses: actions-rs/cargo@v1
4949
with:
5050
command: test
51-
args: -p test-programs-artifacts
51+
args: -p test-programs-artifacts -- --nocapture
5252

5353

5454
check_fmt_and_docs:

src/runtime/block_on.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use super::{Reactor, REACTOR};
22

3-
use core::future::Future;
4-
use core::pin::pin;
5-
use core::task::Waker;
6-
use core::task::{Context, Poll};
3+
use std::future::Future;
4+
use std::pin::pin;
5+
use std::sync::atomic::{AtomicBool, Ordering};
76
use std::sync::Arc;
8-
use std::task::Wake;
7+
use std::task::{Context, Poll, Wake, Waker};
98

109
/// Start the event loop
1110
pub fn block_on<Fut>(fut: Fut) -> Fut::Output
@@ -24,30 +23,54 @@ where
2423
let mut fut = pin!(fut);
2524

2625
// Create a new context to be passed to the future.
27-
let waker = noop_waker();
26+
let root = Arc::new(RootWaker::new());
27+
let waker = Waker::from(root.clone());
2828
let mut cx = Context::from_waker(&waker);
2929

3030
// Either the future completes and we return, or some IO is happening
3131
// and we wait.
3232
let res = loop {
3333
match fut.as_mut().poll(&mut cx) {
3434
Poll::Ready(res) => break res,
35-
Poll::Pending => reactor.block_until(),
35+
Poll::Pending => {
36+
// If some non-pollable based future has marked the root task
37+
// as awake, reset and poll again. otherwise, block until a
38+
// pollable wakes a future.
39+
if root.is_awake() {
40+
root.reset()
41+
} else {
42+
reactor.block_on_pollables()
43+
}
44+
}
3645
}
3746
};
3847
// Clear the singleton
3948
REACTOR.replace(None);
4049
res
4150
}
4251

43-
/// Construct a new no-op waker
44-
// NOTE: we can remove this once <https://github.com/rust-lang/rust/issues/98286> lands
45-
fn noop_waker() -> Waker {
46-
struct NoopWaker;
47-
48-
impl Wake for NoopWaker {
49-
fn wake(self: Arc<Self>) {}
52+
/// This waker is used in the Context of block_on. If a Future executing in
53+
/// the block_on calls context.wake(), it sets this boolean state so that
54+
/// block_on's Future is polled again immediately, rather than waiting for
55+
/// an external (WASI pollable) event before polling again.
56+
struct RootWaker {
57+
wake: AtomicBool,
58+
}
59+
impl RootWaker {
60+
fn new() -> Self {
61+
Self {
62+
wake: AtomicBool::new(false),
63+
}
64+
}
65+
fn is_awake(&self) -> bool {
66+
self.wake.load(Ordering::Relaxed)
67+
}
68+
fn reset(&self) {
69+
self.wake.store(false, Ordering::Relaxed);
70+
}
71+
}
72+
impl Wake for RootWaker {
73+
fn wake(self: Arc<Self>) {
74+
self.wake.store(true, Ordering::Relaxed);
5075
}
51-
52-
Waker::from(Arc::new(NoopWaker))
5376
}

src/runtime/reactor.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,8 @@ impl Reactor {
126126
}
127127
}
128128

129-
/// Block until new events are ready. Calls the respective wakers once done.
130-
///
131-
/// # On Wakers and single-threaded runtimes
132-
///
133-
/// At first glance it might seem silly that this goes through the motions
134-
/// of calling the wakers. The main waker we create here is a `noop` waker:
135-
/// it does nothing. However, it is common and encouraged to use wakers to
136-
/// distinguish between events. Concurrency primitives may construct their
137-
/// own wakers to keep track of identity and wake more precisely. We do not
138-
/// control the wakers construted by other libraries, and it is for this
139-
/// reason that we have to call all the wakers - even if by default they
140-
/// will do nothing.
141-
pub(crate) fn block_until(&self) {
129+
/// Block until at least one pending pollable is ready, waking a pending future.
130+
pub(crate) fn block_on_pollables(&self) {
142131
let reactor = self.inner.borrow();
143132

144133
// We're about to wait for a number of pollables. When they wake we get
@@ -281,4 +270,55 @@ mod test {
281270
.await;
282271
})
283272
}
273+
274+
#[test]
275+
fn progresses_wasi_independent_futures() {
276+
crate::runtime::block_on(async {
277+
let start = wasi::clocks::monotonic_clock::now();
278+
279+
let reactor = Reactor::current();
280+
const LONG_DURATION: u64 = 1_000_000_000;
281+
let later = wasi::clocks::monotonic_clock::subscribe_duration(LONG_DURATION);
282+
let later = reactor.schedule(later);
283+
let mut polled_before = false;
284+
// This is basically futures_lite::future::yield_now, except with a boolean
285+
// `polled_before` so we can definitively observe what happened
286+
let wasi_independent_future = futures_lite::future::poll_fn(|cx| {
287+
if polled_before {
288+
std::task::Poll::Ready(true)
289+
} else {
290+
polled_before = true;
291+
cx.waker().wake_by_ref();
292+
std::task::Poll::Pending
293+
}
294+
});
295+
let later = async {
296+
later.wait_for().await;
297+
false
298+
};
299+
let wasi_independent_future_won =
300+
futures_lite::future::race(wasi_independent_future, later).await;
301+
assert!(
302+
wasi_independent_future_won,
303+
"wasi_independent_future should win the race"
304+
);
305+
const SHORT_DURATION: u64 = LONG_DURATION / 100;
306+
let soon = wasi::clocks::monotonic_clock::subscribe_duration(SHORT_DURATION);
307+
let soon = reactor.schedule(soon);
308+
soon.wait_for().await;
309+
310+
let end = wasi::clocks::monotonic_clock::now();
311+
312+
let duration = end - start;
313+
assert!(
314+
duration > SHORT_DURATION,
315+
"{duration} greater than short duration shows awaited for `soon` properly"
316+
);
317+
// Upper bound is high enough that even the very poor windows CI machines meet it
318+
assert!(
319+
duration < (5 * SHORT_DURATION),
320+
"{duration} less than a reasonable multiple of short duration {SHORT_DURATION} shows did not await for `later`"
321+
);
322+
})
323+
}
284324
}

0 commit comments

Comments
 (0)