mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-14 00:00:12 +02:00
* Make Handle `Send + Sync`. This is an initial implementation making `Handle: Send + Sync`. It uses a `RwLock` to coordinate access to the underlying state storage. An implementation without the lock is left to later. This pass also leaves a lot of dead code that can be removed in later commits. * Remove reactor code related to message passing The previous commit removed the need for using message passing to communicate with the reactor. This commit removes all the unnecessary code.
504 lines
15 KiB
Rust
504 lines
15 KiB
Rust
//! The core reactor driving all I/O
|
|
//!
|
|
//! This module contains the `Core` type which is the reactor for all I/O
|
|
//! happening in `tokio-core`. This reactor (or event loop) is used to drive I/O
|
|
//! resources.
|
|
|
|
use std::fmt;
|
|
use std::io::{self, ErrorKind};
|
|
use std::sync::{Arc, Weak, RwLock};
|
|
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
|
use std::time::{Duration};
|
|
|
|
use futures::{Future, Async};
|
|
use futures::executor::{self, Notify};
|
|
use futures::task::{AtomicTask};
|
|
use mio;
|
|
use mio::event::Evented;
|
|
use slab::Slab;
|
|
|
|
mod io_token;
|
|
|
|
mod poll_evented;
|
|
pub use self::poll_evented::PollEvented;
|
|
|
|
/// Global counter used to assign unique IDs to reactor instances.
|
|
static NEXT_LOOP_ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
|
|
|
/// An event loop.
|
|
///
|
|
/// The event loop is the main source of blocking in an application which drives
|
|
/// all other I/O events and notifications happening. Each event loop can have
|
|
/// multiple handles pointing to it, each of which can then be used to create
|
|
/// various I/O objects to interact with the event loop in interesting ways.
|
|
pub struct Core {
|
|
/// Reuse the `mio::Events` value across calls to poll.
|
|
events: mio::Events,
|
|
|
|
/// State shared between the reactor and the handles.
|
|
inner: Arc<Inner>,
|
|
|
|
/// Used for determining when the future passed to `run` is ready. Once the
|
|
/// registration is passed to `io` above we never touch it again, just keep
|
|
/// it alive.
|
|
_future_registration: mio::Registration,
|
|
future_readiness: Arc<MySetReadiness>,
|
|
}
|
|
|
|
struct Inner {
|
|
/// Unique identifier referencing this reactor.
|
|
id: usize,
|
|
|
|
/// The underlying system event queue.
|
|
io: mio::Poll,
|
|
|
|
/// Dispatch slabs for I/O and futures events
|
|
io_dispatch: RwLock<Slab<ScheduledIo>>,
|
|
}
|
|
|
|
/// An unique ID for a Core
|
|
///
|
|
/// An ID by which different cores may be distinguished. Can be compared and used as an index in
|
|
/// a `HashMap`.
|
|
///
|
|
/// The ID is globally unique and never reused.
|
|
#[derive(Clone,Copy,Eq,PartialEq,Hash,Debug)]
|
|
pub struct CoreId(usize);
|
|
|
|
/// Handle to an event loop, used to construct I/O objects, send messages, and
|
|
/// otherwise interact indirectly with the event loop itself.
|
|
///
|
|
/// Handles can be cloned, and when cloned they will still refer to the
|
|
/// same underlying event loop.
|
|
#[derive(Clone)]
|
|
pub struct Remote {
|
|
id: usize,
|
|
inner: Weak<Inner>,
|
|
}
|
|
|
|
/// A non-sendable handle to an event loop, useful for manufacturing instances
|
|
/// of `LoopData`.
|
|
#[derive(Clone)]
|
|
pub struct Handle {
|
|
remote: Remote,
|
|
}
|
|
|
|
struct ScheduledIo {
|
|
readiness: AtomicUsize,
|
|
reader: AtomicTask,
|
|
writer: AtomicTask,
|
|
}
|
|
|
|
enum Direction {
|
|
Read,
|
|
Write,
|
|
}
|
|
|
|
const TOKEN_FUTURE: mio::Token = mio::Token(1);
|
|
const TOKEN_START: usize = 2;
|
|
|
|
fn _assert_kinds() {
|
|
fn _assert<T: Send + Sync>() {}
|
|
|
|
_assert::<Handle>();
|
|
_assert::<Remote>();
|
|
}
|
|
|
|
impl Core {
|
|
/// Creates a new event loop, returning any error that happened during the
|
|
/// creation.
|
|
pub fn new() -> io::Result<Core> {
|
|
// Create the I/O poller
|
|
let io = try!(mio::Poll::new());
|
|
|
|
// Create a registration for unblocking the reactor when the "run"
|
|
// future becomes ready.
|
|
let future_pair = mio::Registration::new2();
|
|
try!(io.register(&future_pair.0,
|
|
TOKEN_FUTURE,
|
|
mio::Ready::readable(),
|
|
mio::PollOpt::level()));
|
|
|
|
Ok(Core {
|
|
events: mio::Events::with_capacity(1024),
|
|
_future_registration: future_pair.0,
|
|
future_readiness: Arc::new(MySetReadiness(future_pair.1)),
|
|
inner: Arc::new(Inner {
|
|
id: NEXT_LOOP_ID.fetch_add(1, Ordering::Relaxed),
|
|
io: io,
|
|
io_dispatch: RwLock::new(Slab::with_capacity(1)),
|
|
}),
|
|
})
|
|
}
|
|
|
|
/// Returns a handle to this event loop which cannot be sent across threads
|
|
/// but can be used as a proxy to the event loop itself.
|
|
///
|
|
/// Handles are cloneable and clones always refer to the same event loop.
|
|
/// This handle is typically passed into functions that create I/O objects
|
|
/// to bind them to this event loop.
|
|
pub fn handle(&self) -> Handle {
|
|
let remote = self.remote();
|
|
Handle { remote }
|
|
}
|
|
|
|
/// Generates a remote handle to this event loop which can be used to spawn
|
|
/// tasks from other threads into this event loop.
|
|
pub fn remote(&self) -> Remote {
|
|
Remote {
|
|
id: self.inner.id,
|
|
inner: Arc::downgrade(&self.inner),
|
|
}
|
|
}
|
|
|
|
/// Runs a future until completion, driving the event loop while we're
|
|
/// otherwise waiting for the future to complete.
|
|
///
|
|
/// This function will begin executing the event loop and will finish once
|
|
/// the provided future is resolved. Note that the future argument here
|
|
/// crucially does not require the `'static` nor `Send` bounds. As a result
|
|
/// the future will be "pinned" to not only this thread but also this stack
|
|
/// frame.
|
|
///
|
|
/// This function will return the value that the future resolves to once
|
|
/// the future has finished. If the future never resolves then this function
|
|
/// will never return.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method will **not** catch panics from polling the future `f`. If
|
|
/// the future panics then it's the responsibility of the caller to catch
|
|
/// that panic and handle it as appropriate.
|
|
pub fn run<F>(&mut self, f: F) -> Result<F::Item, F::Error>
|
|
where F: Future,
|
|
{
|
|
let mut task = executor::spawn(f);
|
|
let mut future_fired = true;
|
|
|
|
loop {
|
|
if future_fired {
|
|
let res = task.poll_future_notify(&self.future_readiness, 0)?;
|
|
if let Async::Ready(e) = res {
|
|
return Ok(e)
|
|
}
|
|
}
|
|
future_fired = self.poll(None);
|
|
}
|
|
}
|
|
|
|
/// Performs one iteration of the event loop, blocking on waiting for events
|
|
/// for at most `max_wait` (forever if `None`).
|
|
///
|
|
/// It only makes sense to call this method if you've previously spawned
|
|
/// a future onto this event loop.
|
|
///
|
|
/// `loop { lp.turn(None) }` is equivalent to calling `run` with an
|
|
/// empty future (one that never finishes).
|
|
pub fn turn(&mut self, max_wait: Option<Duration>) {
|
|
self.poll(max_wait);
|
|
}
|
|
|
|
fn poll(&mut self, max_wait: Option<Duration>) -> bool {
|
|
// Block waiting for an event to happen, peeling out how many events
|
|
// happened.
|
|
match self.inner.io.poll(&mut self.events, max_wait) {
|
|
Ok(_) => {}
|
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => return false,
|
|
// TODO: This should return an io::Result instead of panic.
|
|
Err(e) => panic!("error in poll: {}", e),
|
|
}
|
|
|
|
// Process all the events that came in, dispatching appropriately
|
|
let mut fired = false;
|
|
for i in 0..self.events.len() {
|
|
let event = self.events.get(i).unwrap();
|
|
let token = event.token();
|
|
trace!("event {:?} {:?}", event.readiness(), event.token());
|
|
|
|
if token == TOKEN_FUTURE {
|
|
self.future_readiness.0.set_readiness(mio::Ready::empty()).unwrap();
|
|
fired = true;
|
|
} else {
|
|
self.dispatch(token, event.readiness());
|
|
}
|
|
}
|
|
|
|
return fired
|
|
}
|
|
|
|
fn dispatch(&mut self, token: mio::Token, ready: mio::Ready) {
|
|
let token = usize::from(token) - TOKEN_START;
|
|
let io_dispatch = self.inner.io_dispatch.read().unwrap();
|
|
|
|
if let Some(io) = io_dispatch.get(token) {
|
|
io.readiness.fetch_or(ready2usize(ready), Ordering::Relaxed);
|
|
if ready.is_writable() {
|
|
io.writer.notify();
|
|
}
|
|
if !(ready & (!mio::Ready::writable())).is_empty() {
|
|
io.reader.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the ID of this loop
|
|
pub fn id(&self) -> CoreId {
|
|
CoreId(self.inner.id)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Core {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("Core")
|
|
.field("id", &self.id())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Inner {
|
|
/// Register an I/O resource with the reactor.
|
|
///
|
|
/// The registration token is returned.
|
|
fn add_source(&self, source: &Evented)
|
|
-> io::Result<usize>
|
|
{
|
|
// Acquire a write lock
|
|
let key = self.io_dispatch.write().unwrap()
|
|
.insert(ScheduledIo {
|
|
readiness: AtomicUsize::new(0),
|
|
reader: AtomicTask::new(),
|
|
writer: AtomicTask::new(),
|
|
});
|
|
|
|
try!(self.io.register(source,
|
|
mio::Token(TOKEN_START + key),
|
|
mio::Ready::readable() |
|
|
mio::Ready::writable() |
|
|
platform::all(),
|
|
mio::PollOpt::edge()));
|
|
|
|
Ok(key)
|
|
}
|
|
|
|
fn deregister_source(&self, source: &Evented) -> io::Result<()> {
|
|
self.io.deregister(source)
|
|
}
|
|
|
|
fn drop_source(&self, token: usize) {
|
|
debug!("dropping I/O source: {}", token);
|
|
self.io_dispatch.write().unwrap().remove(token);
|
|
}
|
|
|
|
/// Registers interest in the I/O resource associated with `token`.
|
|
fn schedule(&self, token: usize, dir: Direction) {
|
|
debug!("scheduling direction for: {}", token);
|
|
let io_dispatch = self.io_dispatch.read().unwrap();
|
|
let sched = io_dispatch.get(token).unwrap();
|
|
|
|
let (task, ready) = match dir {
|
|
Direction::Read => (&sched.reader, !mio::Ready::writable()),
|
|
Direction::Write => (&sched.writer, mio::Ready::writable()),
|
|
};
|
|
|
|
task.register();
|
|
|
|
if sched.readiness.load(Ordering::SeqCst) & ready2usize(ready) != 0 {
|
|
task.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Remote {
|
|
/// Return the ID of the represented Core
|
|
pub fn id(&self) -> CoreId {
|
|
CoreId(self.id)
|
|
}
|
|
|
|
/// Attempts to "promote" this remote to a handle, if possible.
|
|
///
|
|
/// This function is intended for structures which typically work through a
|
|
/// `Remote` but want to optimize runtime when the remote doesn't actually
|
|
/// leave the thread of the original reactor. This will attempt to return a
|
|
/// handle if the `Remote` is on the same thread as the event loop and the
|
|
/// event loop is running.
|
|
///
|
|
/// If this `Remote` has moved to a different thread or if the event loop is
|
|
/// running, then `None` may be returned. If you need to guarantee access to
|
|
/// a `Handle`, then you can call this function and fall back to using
|
|
/// `spawn` above if it returns `None`.
|
|
pub fn handle(&self) -> Option<Handle> {
|
|
let remote = self.clone();
|
|
Some(Handle { remote } )
|
|
}
|
|
|
|
/// Spawns a new future into the event loop this remote is associated with.
|
|
///
|
|
/// This function takes a closure which is executed within the context of
|
|
/// the I/O loop itself. The future returned by the closure will be
|
|
/// scheduled on the event loop and run to completion.
|
|
///
|
|
/// Note that while the closure, `F`, requires the `Send` bound as it might
|
|
/// cross threads, the future `R` does not.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method will **not** catch panics from polling the future `f`. If
|
|
/// the future panics then it's the responsibility of the caller to catch
|
|
/// that panic and handle it as appropriate.
|
|
pub(crate) fn run<F>(&self, f: F)
|
|
where F: FnOnce(&Handle) + Send + 'static,
|
|
{
|
|
let handle = self.handle().unwrap();
|
|
f(&handle);
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Remote {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("Remote")
|
|
.field("id", &self.id())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Handle {
|
|
/// Returns a reference to the underlying remote handle to the event loop.
|
|
pub fn remote(&self) -> &Remote {
|
|
&self.remote
|
|
}
|
|
|
|
/// Return the ID of the represented Core
|
|
pub fn id(&self) -> CoreId {
|
|
self.remote.id()
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Handle {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("Handle")
|
|
.field("id", &self.id())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
struct MySetReadiness(mio::SetReadiness);
|
|
|
|
impl Notify for MySetReadiness {
|
|
fn notify(&self, _id: usize) {
|
|
self.0.set_readiness(mio::Ready::readable())
|
|
.expect("failed to set readiness");
|
|
}
|
|
}
|
|
|
|
trait FnBox: Send + 'static {
|
|
fn call_box(self: Box<Self>, lp: &Core);
|
|
}
|
|
|
|
impl<F: FnOnce(&Core) + Send + 'static> FnBox for F {
|
|
fn call_box(self: Box<Self>, lp: &Core) {
|
|
(*self)(lp)
|
|
}
|
|
}
|
|
|
|
fn read_ready() -> mio::Ready {
|
|
mio::Ready::readable() | platform::hup()
|
|
}
|
|
|
|
const READ: usize = 1 << 0;
|
|
const WRITE: usize = 1 << 1;
|
|
|
|
fn ready2usize(ready: mio::Ready) -> usize {
|
|
let mut bits = 0;
|
|
if ready.is_readable() {
|
|
bits |= READ;
|
|
}
|
|
if ready.is_writable() {
|
|
bits |= WRITE;
|
|
}
|
|
bits | platform::ready2usize(ready)
|
|
}
|
|
|
|
fn usize2ready(bits: usize) -> mio::Ready {
|
|
let mut ready = mio::Ready::empty();
|
|
if bits & READ != 0 {
|
|
ready.insert(mio::Ready::readable());
|
|
}
|
|
if bits & WRITE != 0 {
|
|
ready.insert(mio::Ready::writable());
|
|
}
|
|
ready | platform::usize2ready(bits)
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
|
mod platform {
|
|
use mio::Ready;
|
|
use mio::unix::UnixReady;
|
|
|
|
pub fn aio() -> Ready {
|
|
UnixReady::aio().into()
|
|
}
|
|
|
|
pub fn all() -> Ready {
|
|
hup() | aio()
|
|
}
|
|
|
|
pub fn hup() -> Ready {
|
|
UnixReady::hup().into()
|
|
}
|
|
|
|
const HUP: usize = 1 << 2;
|
|
const ERROR: usize = 1 << 3;
|
|
const AIO: usize = 1 << 4;
|
|
|
|
pub fn ready2usize(ready: Ready) -> usize {
|
|
let ready = UnixReady::from(ready);
|
|
let mut bits = 0;
|
|
if ready.is_aio() {
|
|
bits |= AIO;
|
|
}
|
|
if ready.is_error() {
|
|
bits |= ERROR;
|
|
}
|
|
if ready.is_hup() {
|
|
bits |= HUP;
|
|
}
|
|
bits
|
|
}
|
|
|
|
pub fn usize2ready(bits: usize) -> Ready {
|
|
let mut ready = UnixReady::from(Ready::empty());
|
|
if bits & AIO != 0 {
|
|
ready.insert(UnixReady::aio());
|
|
}
|
|
if bits & HUP != 0 {
|
|
ready.insert(UnixReady::hup());
|
|
}
|
|
if bits & ERROR != 0 {
|
|
ready.insert(UnixReady::error());
|
|
}
|
|
ready.into()
|
|
}
|
|
}
|
|
|
|
#[cfg(any(windows, target_os = "fuchsia"))]
|
|
mod platform {
|
|
use mio::Ready;
|
|
|
|
pub fn all() -> Ready {
|
|
// No platform-specific Readinesses for Windows
|
|
Ready::empty()
|
|
}
|
|
|
|
pub fn hup() -> Ready {
|
|
Ready::empty()
|
|
}
|
|
|
|
pub fn ready2usize(_r: Ready) -> usize {
|
|
0
|
|
}
|
|
|
|
pub fn usize2ready(_r: usize) -> Ready {
|
|
Ready::empty()
|
|
}
|
|
}
|