This commit is contained in:
Stjepan Glavina 2020-04-24 12:38:17 +02:00
parent 6ab678ccaa
commit de27769b98
4 changed files with 76 additions and 56 deletions

View File

@ -157,14 +157,14 @@ impl Reactor {
///
/// This doesn't have strong guarantees. If there are ready events, they may or may not be
/// processed depending on whether the reactor is locked.
pub fn poll(&self) -> io::Result<()> {
if let Some(events) = self.events.try_lock() {
let reactor = self;
let mut lock = ReactorLock { reactor, events };
pub fn poll(&self) -> io::Result<Option<usize>> {
if let Some(mut lock) = self.try_lock() {
// React to events without blocking.
lock.react(false)?;
let n = lock.react(false)?;
Ok(Some(n))
} else {
Ok(None)
}
Ok(())
}
/// Locks the reactor.
@ -173,6 +173,14 @@ impl Reactor {
let events = self.events.lock().await;
ReactorLock { reactor, events }
}
/// Attempts to lock the reactor.
pub fn try_lock(&self) -> Option<ReactorLock<'_>> {
self.events.try_lock().map(|events| {
let reactor = self;
ReactorLock { reactor, events }
})
}
}
/// Polls the reactor for I/O events and wakes up tasks.
@ -183,12 +191,18 @@ pub(crate) struct ReactorLock<'a> {
impl ReactorLock<'_> {
/// Blocks until at least one event is processed.
pub fn wait(&mut self) -> io::Result<()> {
pub fn wait(&mut self) -> io::Result<usize> {
self.react(true)
}
/// TODO
pub fn poll(&mut self) -> io::Result<usize> {
self.react(false)
}
/// Processes new events, optionally blocking until the first event.
fn react(&mut self, block: bool) -> io::Result<()> {
fn react(&mut self, block: bool) -> io::Result<usize> {
let mut count = 0;
loop {
// Fire timers and compute the timeout until the next event.
let timeout = self.fire_timers();
@ -207,7 +221,7 @@ impl ReactorLock<'_> {
// The timeout was hit, so check for timers again.
Ok(0) => {
self.fire_timers();
return Ok(());
return Ok(0); // TODO
}
// At least one I/O event occured.
@ -225,11 +239,12 @@ impl ReactorLock<'_> {
// Wake up tasks waiting on I/O.
for w in wakers.drain(..) {
count += 1; // TODO: don't forget timers
w.wake();
}
}
return Ok(());
return Ok(count);
}
// The syscall was interrupted - recompute the timeout and restart.

View File

@ -128,6 +128,7 @@ pub fn run<T>(future: impl Future<Output = T>) -> T {
//
// This way we make sure that if any changes happen that might give us new work will
// unblock epoll/kevent/wepoll and let us continue the loop.
let mut step = 0;
loop {
// 1. Poll the main future.
if let Poll::Ready(val) = throttle::setup(|| future.as_mut().poll(cx)) {
@ -137,15 +138,51 @@ pub fn run<T>(future: impl Future<Output = T>) -> T {
let more_local = local.execute();
// 3. Run a batch of tasks in the work-stealing executor.
let more_worker = worker.execute();
// TODO: wake every time a worker is dropped?
// 4. Poll the reactor.
reactor.poll().expect("failure while polling I/O");
if let Some(mut reactor_lock) = reactor.try_lock() {
step = 0;
// Clear the two I/O events.
let local_ev = local.event().clear();
let ws_ev = ws_executor.event().clear();
if reactor_lock.poll().expect("failure while polling I/O") > 0 {
continue;
}
if more_local || more_worker {
continue;
}
// If any of the two I/O events has been triggered, continue the loop.
if local_ev || ws_ev {
continue;
}
// Block until an I/O event occurs.
reactor_lock.wait().expect("failure while waiting on I/O");
local.event().clear();
ws_executor.event().clear();
continue;
}
// If there is more work in the thread-local or the work-stealing executor, continue
// the loop.
if more_local || more_worker {
step = 0;
continue;
}
step += 1;
if step <= 2 {
std::thread::yield_now();
continue;
}
step = 0;
// Prepare for blocking until the reactor is locked or `local.event()` is triggered.
//
// Note that there is no need to wait for `ws_executor.event()`. If the reactor is
@ -173,6 +210,9 @@ pub fn run<T>(future: impl Future<Output = T>) -> T {
// Block until an I/O event occurs.
reactor_lock.wait().expect("failure while waiting on I/O");
local.event().clear();
ws_executor.event().clear();
}
}
})

View File

@ -115,8 +115,7 @@ impl ThreadLocalExecutor {
}
}
// Poll the reactor and drain the injector queue. We do this occasionally to make
// execution more fair to all tasks involved.
// Drain the injector queue occasionally to make execution more fair.
self.fetch();
}
@ -136,11 +135,8 @@ impl ThreadLocalExecutor {
self.queue.borrow_mut().pop_front()
}
/// Polls the reactor and moves all tasks from the injector queue into the main queue.
/// Moves all tasks from the injector queue into the main queue.
fn fetch(&self) {
// The reactor might wake tasks belonging to this executor.
Reactor::get().poll().expect("failure while polling I/O");
// Move tasks from the injector queue into the main queue.
let mut queue = self.queue.borrow_mut();
while let Ok(r) = self.injector.pop() {

View File

@ -33,7 +33,6 @@ use scoped_tls_hkt::scoped_thread_local;
use slab::Slab;
use crate::io_event::IoEvent;
use crate::reactor::Reactor;
use crate::task::{Runnable, Task};
use crate::throttle;
@ -180,10 +179,11 @@ impl Worker<'_> {
}
}
// Flush the slot, grab some tasks from the global queue, and poll the reactor. We do
// this occasionally to make execution more fair to all tasks involved.
self.push(None);
self.fetch();
if let Some(r) = retry_steal(|| self.executor.injector.steal_batch_and_pop(&self.queue))
{
self.push(Some(r));
}
}
// There are likely more tasks to run.
@ -199,9 +199,6 @@ impl Worker<'_> {
if let Some(r) = self.slot.replace(runnable.into()) {
// If the slot had a task, push it into the queue.
self.queue.push(r);
// Notify other workers that there are stealable tasks.
self.executor.event.notify();
}
}
@ -212,39 +209,14 @@ impl Worker<'_> {
return Some(r);
}
// If not, fetch more tasks from the injector queue, the reactor, or other workers.
self.fetch();
// Check the slot and the queue again.
self.slot.take().or_else(|| self.queue.pop())
}
/// Steals from the injector and polls the reactor, or steals from other workers if that fails.
fn fetch(&self) {
// Try stealing from the global queue.
if let Some(r) = retry_steal(|| self.executor.injector.steal_batch_and_pop(&self.queue)) {
// Push the task, but don't return -- let's not forget to poll the reactor.
self.push(r);
return Some(r);
}
// Poll the reactor.
Reactor::get().poll().expect("failure while polling I/O");
// If there is at least one task in the slot, return.
if let Some(r) = self.slot.take() {
self.slot.set(Some(r));
return;
}
// If there is at least one task in the queue, return.
if !self.queue.is_empty() {
return;
}
// Still no tasks found - our last hope is to steal from other workers.
// Try stealing from other workers.
let stealers = self.executor.stealers.read().unwrap();
if let Some(r) = retry_steal(|| {
retry_steal(|| {
// Pick a random starting point in the iterator list and rotate the list.
let n = stealers.len();
let start = fast_random(n);
@ -258,10 +230,7 @@ impl Worker<'_> {
// that's the collected result and we'll retry from the beginning.
iter.map(|(_, s)| s.steal_batch_and_pop(&self.queue))
.collect()
}) {
// Push the stolen task.
self.push(r);
}
})
}
}