mirror of https://github.com/smol-rs/smol
119 lines
5.0 KiB
Rust
119 lines
5.0 KiB
Rust
use std::future::Future;
|
|
use std::task::{Context, Poll};
|
|
|
|
use futures::future::{self, Either};
|
|
|
|
use crate::block_on;
|
|
use crate::context;
|
|
use crate::reactor::Reactor;
|
|
use crate::thread_local::ThreadLocalExecutor;
|
|
use crate::throttle;
|
|
use crate::work_stealing::WorkStealingExecutor;
|
|
|
|
/// Runs executors and polls the reactor.
|
|
///
|
|
/// This function simultaneously runs the thread-local executor, runs the work-stealing
|
|
/// executor, and polls the reactor for I/O events and timers. At least one thread has to be
|
|
/// calling [`run()`] in order for futures waiting on I/O and timers to get notified.
|
|
///
|
|
/// # Examples
|
|
/// TODO a thread-pool example with num_cpus::get().max(1)
|
|
/// TODO a stoppable thread-pool with channels
|
|
///
|
|
/// ```no_run
|
|
/// use futures::future;
|
|
/// use std::thread;
|
|
///
|
|
/// for _ in 0..num_cpus::get().max(1) {
|
|
/// thread::spawn(|| smol::run(future::pending::<()>()));
|
|
/// }
|
|
/// ```
|
|
pub fn run<T>(future: impl Future<Output = T>) -> T {
|
|
// Create a thread-local executor and a worker in the work-stealing executor.
|
|
let local = ThreadLocalExecutor::new();
|
|
let ws_executor = WorkStealingExecutor::get();
|
|
let worker = ws_executor.worker();
|
|
let reactor = Reactor::get();
|
|
|
|
// Create a waker that triggers an I/O event in the thread-local scheduler.
|
|
let ev = local.event().clone();
|
|
let waker = async_task::waker_fn(move || ev.set());
|
|
let cx = &mut Context::from_waker(&waker);
|
|
futures::pin_mut!(future);
|
|
|
|
// Set up tokio (if enabled) and the thread-locals before execution begins.
|
|
let enter = context::enter;
|
|
let enter = |f| local.enter(|| enter(f));
|
|
let enter = |f| worker.enter(|| enter(f));
|
|
|
|
enter(|| {
|
|
// We run four components at the same time, treating them all fairly and making sure none
|
|
// of them get starved:
|
|
//
|
|
// 1. `future` - the main future.
|
|
// 2. `local - the thread-local executor.
|
|
// 3. `worker` - the work-stealing executor.
|
|
// 4. `reactor` - the reactor.
|
|
//
|
|
// When all four components are out of work, we block the current thread on
|
|
// epoll/kevent/wepoll. If new work comes in that isn't naturally triggered by an I/O event
|
|
// registered with `Async` handles, we use `IoEvent`s to simulate an I/O event that will
|
|
// unblock the thread:
|
|
//
|
|
// - When the main future is woken, `local.event()` is triggered.
|
|
// - When thread-local executor gets new work, `local.event()` is triggered.
|
|
// - When work-stealing executor gets new work, `ws_executor.event()` is triggered.
|
|
// - When a new earliest timer is registered, `reactor.event()` is triggered.
|
|
//
|
|
// 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.
|
|
loop {
|
|
// 1. Poll the main future.
|
|
if let Poll::Ready(val) = throttle::setup(|| future.as_mut().poll(cx)) {
|
|
return val;
|
|
}
|
|
// 2. Run a batch of tasks in the thread-local executor.
|
|
let more_local = local.execute();
|
|
// 3. Run a batch of tasks in the work-stealing executor.
|
|
let more_worker = worker.execute();
|
|
// 4. Poll the reactor.
|
|
reactor.poll().expect("failure while polling I/O");
|
|
|
|
// If there is more work in the thread-local or the work-stealing executor, continue
|
|
// the loop.
|
|
if more_local || more_worker {
|
|
continue;
|
|
}
|
|
|
|
// 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
|
|
// locked immediately, we'll check for the I/O event right after that anyway.
|
|
//
|
|
// If some other worker is holding the reactor locked, it will be unblocked as soon as
|
|
// the I/O event is triggered. Then, another worker will be allowed to lock the
|
|
// reactor, and will be unblocked if there is more work to do. Every worker triggers
|
|
// `ws_executor.event()` each time it finds a runnable task.
|
|
let lock = reactor.lock();
|
|
let ready = local.event().ready();
|
|
futures::pin_mut!(lock);
|
|
futures::pin_mut!(ready);
|
|
|
|
// Block until either the reactor is locked or `local.event()` is triggered.
|
|
if let Either::Left((mut reactor_lock, _)) = block_on(future::select(lock, ready)) {
|
|
// Clear the two I/O events.
|
|
let local_ev = local.event().clear();
|
|
let ws_ev = ws_executor.event().clear();
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
})
|
|
}
|