Skip to content

Commit 6582494

Browse files
committed
inline poller into reactor, only poll on pollables that have waiters, add more unit tests
1 parent a39473a commit 6582494

File tree

3 files changed

+111
-103
lines changed

3 files changed

+111
-103
lines changed

src/runtime/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#![warn(missing_docs, unreachable_pub)]
1212

1313
mod block_on;
14-
mod polling;
1514
mod reactor;
1615

1716
pub use block_on::block_on;

src/runtime/polling.rs

Lines changed: 0 additions & 88 deletions
This file was deleted.

src/runtime/reactor.rs

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
use super::{
2-
polling::{EventKey, Poller},
3-
REACTOR,
4-
};
1+
use super::REACTOR;
52

63
use core::cell::RefCell;
74
use core::future;
85
use core::pin::Pin;
96
use core::task::{Context, Poll, Waker};
7+
use slab::Slab;
108
use std::collections::HashMap;
119
use std::rc::Rc;
1210
use wasi::io::poll::Pollable;
1311

12+
/// A key representing an entry into the poller
13+
#[repr(transparent)]
14+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
15+
pub(crate) struct EventKey(pub(crate) usize);
16+
1417
#[derive(Debug, PartialEq, Eq, Hash)]
1518
struct Registration {
1619
key: EventKey,
@@ -83,7 +86,7 @@ pub struct Reactor {
8386
/// a lock of the whole.
8487
#[derive(Debug)]
8588
struct InnerReactor {
86-
poller: Poller,
89+
pollables: Slab<Pollable>,
8790
wakers: HashMap<Waitee, Waker>,
8891
}
8992

@@ -105,7 +108,7 @@ impl Reactor {
105108
pub(crate) fn new() -> Self {
106109
Self {
107110
inner: Rc::new(RefCell::new(InnerReactor {
108-
poller: Poller::new(),
111+
pollables: Slab::new(),
109112
wakers: HashMap::new(),
110113
})),
111114
}
@@ -124,8 +127,42 @@ impl Reactor {
124127
/// reason that we have to call all the wakers - even if by default they
125128
/// will do nothing.
126129
pub(crate) fn block_until(&self) {
127-
let mut reactor = self.inner.borrow_mut();
128-
for key in reactor.poller.block_until() {
130+
let reactor = self.inner.borrow();
131+
132+
// We're about to wait for a number of pollables. When they wake we get
133+
// the *indexes* back for the pollables whose events were available - so
134+
// we need to be able to associate the index with the right waker.
135+
136+
// We start by iterating over the pollables, and keeping note of which
137+
// pollable belongs to which waker index
138+
let mut indexes = Vec::with_capacity(reactor.wakers.len());
139+
let mut targets = Vec::with_capacity(reactor.wakers.len());
140+
for waitee in reactor.wakers.keys() {
141+
let pollable_index = waitee.pollable.0.key;
142+
indexes.push(pollable_index);
143+
targets.push(&reactor.pollables[pollable_index.0]);
144+
}
145+
146+
debug_assert_ne!(
147+
targets.len(),
148+
0,
149+
"Attempting to block on an empty list of pollables - without any pending work, no progress can be made and wasi::io::poll::poll will trap"
150+
);
151+
152+
println!("polling for {indexes:?}");
153+
// Now that we have that association, we're ready to poll our targets.
154+
// This will block until an event has completed.
155+
let ready_indexes = wasi::io::poll::poll(&targets);
156+
157+
// Once we have the indexes for which pollables are available, we need
158+
// to convert it back to the right keys for the wakers. Earlier we
159+
// established a positional index -> waker key relationship, so we can
160+
// go right ahead and perform a lookup there.
161+
let ready_keys = ready_indexes
162+
.into_iter()
163+
.map(|index| indexes[index as usize]);
164+
165+
for key in ready_keys {
129166
for (waitee, waker) in reactor.wakers.iter() {
130167
if waitee.pollable.0.key == key {
131168
println!("waking {key:?}");
@@ -138,15 +175,15 @@ impl Reactor {
138175
/// Turn a wasi [`Pollable`] into an [`AsyncPollable`]
139176
pub fn schedule(&self, pollable: Pollable) -> AsyncPollable {
140177
let mut reactor = self.inner.borrow_mut();
141-
let key = reactor.poller.insert(pollable);
178+
let key = EventKey(reactor.pollables.insert(pollable));
142179
println!("schedule pollable as {key:?}");
143180
AsyncPollable(Rc::new(Registration { key }))
144181
}
145182

146183
fn deregister_event(&self, key: EventKey) {
147184
let mut reactor = self.inner.borrow_mut();
148185
println!("deregister {key:?}",);
149-
reactor.poller.remove(key);
186+
reactor.pollables.remove(key.0);
150187
}
151188

152189
fn deregister_waitee(&self, waitee: &Waitee) {
@@ -158,8 +195,8 @@ impl Reactor {
158195
fn ready(&self, waitee: &Waitee, waker: &Waker) -> bool {
159196
let mut reactor = self.inner.borrow_mut();
160197
let ready = reactor
161-
.poller
162-
.get(&waitee.pollable.0.key)
198+
.pollables
199+
.get(waitee.pollable.0.key.0)
163200
.expect("only live EventKey can be checked for readiness")
164201
.ready();
165202
if !ready {
@@ -180,13 +217,73 @@ impl Reactor {
180217
#[cfg(test)]
181218
mod test {
182219
use super::*;
220+
// Using WASMTIME_LOG, observe that this test doesn't even call poll() - the pollable is ready
221+
// immediately.
183222
#[test]
184-
fn reactor_subscribe_duration() {
223+
fn subscribe_no_duration() {
185224
crate::runtime::block_on(async {
186225
let reactor = Reactor::current();
187-
let pollable = wasi::clocks::monotonic_clock::subscribe_duration(1000);
226+
let pollable = wasi::clocks::monotonic_clock::subscribe_duration(0);
188227
let sched = reactor.schedule(pollable);
189228
sched.wait_for().await;
190229
})
191230
}
231+
// Using WASMTIME_LOG, observe that this test calls poll() until the timer is ready.
232+
#[test]
233+
fn subscribe_some_duration() {
234+
crate::runtime::block_on(async {
235+
let reactor = Reactor::current();
236+
let pollable = wasi::clocks::monotonic_clock::subscribe_duration(10_000_000);
237+
let sched = reactor.schedule(pollable);
238+
sched.wait_for().await;
239+
})
240+
}
241+
242+
// Using WASMTIME_LOG, observe that this test results in a single poll() on the second
243+
// subscription, rather than spinning in poll() with first subscription, which is instantly
244+
// ready, but not what the waker requests.
245+
#[test]
246+
fn subscribe_multiple_durations() {
247+
crate::runtime::block_on(async {
248+
let reactor = Reactor::current();
249+
let now = wasi::clocks::monotonic_clock::subscribe_duration(0);
250+
let soon = wasi::clocks::monotonic_clock::subscribe_duration(10_000_000);
251+
let now = reactor.schedule(now);
252+
let soon = reactor.schedule(soon);
253+
soon.wait_for().await;
254+
drop(now)
255+
})
256+
}
257+
258+
// Using WASMTIME_LOG, observe that this test results in two calls to poll(), one with both
259+
// pollables because both are awaiting, and one with just the later pollable.
260+
#[test]
261+
fn subscribe_multiple_durations_zipped() {
262+
crate::runtime::block_on(async {
263+
let reactor = Reactor::current();
264+
let start = wasi::clocks::monotonic_clock::now();
265+
let soon = wasi::clocks::monotonic_clock::subscribe_duration(10_000_000);
266+
let later = wasi::clocks::monotonic_clock::subscribe_duration(40_000_000);
267+
let soon = reactor.schedule(soon);
268+
let later = reactor.schedule(later);
269+
270+
futures_lite::future::zip(
271+
async move {
272+
soon.wait_for().await;
273+
println!(
274+
"*** subscribe_duration(soon) ready ({})",
275+
wasi::clocks::monotonic_clock::now() - start
276+
);
277+
},
278+
async move {
279+
later.wait_for().await;
280+
println!(
281+
"*** subscribe_duration(later) ready ({})",
282+
wasi::clocks::monotonic_clock::now() - start
283+
);
284+
},
285+
)
286+
.await;
287+
})
288+
}
192289
}

0 commit comments

Comments
 (0)