mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-03 00:00:05 +02:00
Don't store an Arc in ReadinessStream
This commit contains a few refactorings, but the major goal is to remove the `Arc` that's stored inside of each `ReadinessStream` and `Scheduled` slot in the event loop. The original purpose of this `Arc` was to share the I/O object among the concrete handle itself and the event loop. The event loop would then change how the socket is registered over time and then deregister it when it gets a "shutdown request". Nowadays, however, once an I/O object is registered with the event loop it's never updated. Additionally, we don't actually need to call `deregister` but can rather just instead close the I/O object itself and let the kernel/event loop take care of the cleanup. All we need to do on deregistering is free up the slab entry. The major result of this commit is that I/O objects no longer need to be `Sync` (as they're not stored in an `Arc`). Instead they just need to be `Send + 'static` as one might otherwise expect. Along the way this also refactors a few pieces here and there to make more sense in this new scheme. The `ReadinessStream` type now has a type parameter indicating an owned reference to the I/O object it wraps. This can be accessed via the `get_ref` and `get_mut` methods. Additionally I/O tokens on the event loop are now a full-fledged `IoToken` type which we can change in the future if we need to.
This commit is contained in:
+102
-100
@@ -80,7 +80,7 @@ pub struct LoopPin {
|
||||
}
|
||||
|
||||
struct Scheduled {
|
||||
source: IoSource,
|
||||
readiness: Arc<AtomicUsize>,
|
||||
reader: Option<TaskHandle>,
|
||||
writer: Option<TaskHandle>,
|
||||
}
|
||||
@@ -97,7 +97,6 @@ enum Direction {
|
||||
}
|
||||
|
||||
enum Message {
|
||||
AddSource(IoSource, Arc<Slot<io::Result<usize>>>),
|
||||
DropSource(usize),
|
||||
Schedule(usize, TaskHandle, Direction),
|
||||
AddTimeout(Instant, Arc<Slot<io::Result<TimeoutToken>>>),
|
||||
@@ -107,29 +106,6 @@ enum Message {
|
||||
Drop(DropBox<dropbox::MyDrop>),
|
||||
}
|
||||
|
||||
/// Type of I/O objects inserted into the event loop, created by `Source::new`.
|
||||
pub struct Source<E: ?Sized> {
|
||||
readiness: AtomicUsize,
|
||||
io: E,
|
||||
}
|
||||
|
||||
/// I/O objects inserted into the event loop
|
||||
pub type IoSource = Arc<Source<mio::Evented + Sync + Send>>;
|
||||
|
||||
fn register(poll: &mio::Poll,
|
||||
token: usize,
|
||||
sched: &Scheduled) -> io::Result<()> {
|
||||
poll.register(&sched.source.io,
|
||||
mio::Token(token),
|
||||
mio::EventSet::readable() | mio::EventSet::writable(),
|
||||
mio::PollOpt::edge())
|
||||
}
|
||||
|
||||
fn deregister(poll: &mio::Poll, sched: &Scheduled) {
|
||||
// TODO: handle error
|
||||
poll.deregister(&sched.source.io).unwrap();
|
||||
}
|
||||
|
||||
impl Loop {
|
||||
/// Creates a new event loop, returning any error that happened during the
|
||||
/// creation.
|
||||
@@ -334,11 +310,11 @@ impl Loop {
|
||||
if let Some(sched) = self.dispatch.borrow_mut().get_mut(token) {
|
||||
if event.kind().is_readable() {
|
||||
reader = sched.reader.take();
|
||||
sched.source.readiness.fetch_or(1, Ordering::Relaxed);
|
||||
sched.readiness.fetch_or(1, Ordering::Relaxed);
|
||||
}
|
||||
if event.kind().is_writable() {
|
||||
writer = sched.writer.take();
|
||||
sched.source.readiness.fetch_or(2, Ordering::Relaxed);
|
||||
sched.readiness.fetch_or(2, Ordering::Relaxed);
|
||||
}
|
||||
} else {
|
||||
debug!("notified on {} which no longer exists", token);
|
||||
@@ -382,10 +358,10 @@ impl Loop {
|
||||
CURRENT_LOOP.set(&self, || handle.unpark());
|
||||
}
|
||||
|
||||
fn add_source(&self, source: IoSource) -> io::Result<usize> {
|
||||
fn add_source(&self, source: &mio::Evented) -> io::Result<IoToken> {
|
||||
debug!("adding a new I/O source");
|
||||
let sched = Scheduled {
|
||||
source: source,
|
||||
readiness: Arc::new(AtomicUsize::new(0)),
|
||||
reader: None,
|
||||
writer: None,
|
||||
};
|
||||
@@ -395,14 +371,20 @@ impl Loop {
|
||||
dispatch.grow(amt);
|
||||
}
|
||||
let entry = dispatch.vacant_entry().unwrap();
|
||||
try!(register(&self.io, entry.index(), &sched));
|
||||
Ok(entry.insert(sched).index())
|
||||
try!(self.io.register(source,
|
||||
mio::Token(entry.index()),
|
||||
mio::EventSet::readable() |
|
||||
mio::EventSet::writable(),
|
||||
mio::PollOpt::edge()));
|
||||
Ok(IoToken {
|
||||
readiness: sched.readiness.clone(),
|
||||
token: entry.insert(sched).index(),
|
||||
})
|
||||
}
|
||||
|
||||
fn drop_source(&self, token: usize) {
|
||||
debug!("dropping I/O source: {}", token);
|
||||
let sched = self.dispatch.borrow_mut().remove(token).unwrap();
|
||||
deregister(&self.io, &sched);
|
||||
self.dispatch.borrow_mut().remove(token).unwrap();
|
||||
}
|
||||
|
||||
fn schedule(&self, token: usize, wake: TaskHandle, dir: Direction) {
|
||||
@@ -414,10 +396,10 @@ impl Loop {
|
||||
Direction::Read => (&mut sched.reader, 1),
|
||||
Direction::Write => (&mut sched.writer, 2),
|
||||
};
|
||||
let ready = sched.source.readiness.load(Ordering::SeqCst);
|
||||
let ready = sched.readiness.load(Ordering::SeqCst);
|
||||
if ready & bit != 0 {
|
||||
*slot = None;
|
||||
sched.source.readiness.store(ready & !bit, Ordering::SeqCst);
|
||||
sched.readiness.store(ready & !bit, Ordering::SeqCst);
|
||||
Some(wake)
|
||||
} else {
|
||||
*slot = Some(wake);
|
||||
@@ -472,11 +454,6 @@ impl Loop {
|
||||
|
||||
fn notify(&self, msg: Message) {
|
||||
match msg {
|
||||
Message::AddSource(source, slot) => {
|
||||
// This unwrap() should always be ok as we're the only producer
|
||||
slot.try_produce(self.add_source(source))
|
||||
.ok().expect("interference with try_produce");
|
||||
}
|
||||
Message::DropSource(tok) => self.drop_source(tok),
|
||||
Message::Schedule(tok, wake, dir) => self.schedule(tok, wake, dir),
|
||||
|
||||
@@ -545,19 +522,20 @@ impl LoopHandle {
|
||||
///
|
||||
/// When a new I/O object is created it needs to be communicated to the
|
||||
/// event loop to ensure that it's registered and ready to receive
|
||||
/// notifications. The event loop with then respond with a unique token that
|
||||
/// this handle can be identified with (the resolved value of the returned
|
||||
/// future).
|
||||
/// notifications. The event loop with then respond back with the I/O object
|
||||
/// and a token which can be used to send more messages to the event loop.
|
||||
///
|
||||
/// This token is then passed in turn to each of the methods below to
|
||||
/// interact with notifications on the I/O object itself.
|
||||
/// The token returned is then passed in turn to each of the methods below
|
||||
/// to interact with notifications on the I/O object itself.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The returned future will panic if the event loop this handle is
|
||||
/// associated with has gone away, or if there is an error communicating
|
||||
/// with the event loop.
|
||||
pub fn add_source(&self, source: IoSource) -> AddSource {
|
||||
pub fn add_source<E>(&self, source: E) -> AddSource<E>
|
||||
where E: mio::Evented + Send + 'static,
|
||||
{
|
||||
AddSource {
|
||||
inner: LoopFuture {
|
||||
loop_handle: self.clone(),
|
||||
@@ -567,15 +545,19 @@ impl LoopHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Begin listening for read events on an event loop.
|
||||
/// Schedule the current future task to receive a notification when the
|
||||
/// corresponding I/O object is readable.
|
||||
///
|
||||
/// Once an I/O object has been registered with the event loop through the
|
||||
/// `add_source` method, this method can be used with the assigned token to
|
||||
/// begin awaiting read notifications.
|
||||
/// notify the current future task when the next read notification comes in.
|
||||
///
|
||||
/// Currently the current task will be notified with *edge* semantics. This
|
||||
/// means that whenever the underlying I/O object changes state, e.g. it was
|
||||
/// not readable and now it is, then a notification will be sent.
|
||||
/// The current task will only receive a notification **once** and to
|
||||
/// receive further notifications it will need to call `schedule_read`
|
||||
/// again.
|
||||
///
|
||||
/// > **Note**: This method should generally not be used directly, but
|
||||
/// > rather the `ReadinessStream` type should be used instead.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -585,19 +567,24 @@ impl LoopHandle {
|
||||
///
|
||||
/// This function will also panic if there is not a currently running future
|
||||
/// task.
|
||||
pub fn schedule_read(&self, tok: usize) {
|
||||
self.send(Message::Schedule(tok, task::park(), Direction::Read));
|
||||
pub fn schedule_read(&self, tok: &IoToken) {
|
||||
self.send(Message::Schedule(tok.token, task::park(), Direction::Read));
|
||||
}
|
||||
|
||||
/// Begin listening for write events on an event loop.
|
||||
/// Schedule the current future task to receive a notification when the
|
||||
/// corresponding I/O object is writable.
|
||||
///
|
||||
/// Once an I/O object has been registered with the event loop through the
|
||||
/// `add_source` method, this method can be used with the assigned token to
|
||||
/// begin awaiting write notifications.
|
||||
/// notify the current future task when the next write notification comes
|
||||
/// in.
|
||||
///
|
||||
/// Currently the current task will be notified with *edge* semantics. This
|
||||
/// means that whenever the underlying I/O object changes state, e.g. it was
|
||||
/// not writable and now it is, then a notification will be sent.
|
||||
/// The current task will only receive a notification **once** and to
|
||||
/// receive further notifications it will need to call `schedule_write`
|
||||
/// again.
|
||||
///
|
||||
/// > **Note**: This method should generally not be used directly, but
|
||||
/// > rather the `ReadinessStream` type should be used instead.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -607,8 +594,8 @@ impl LoopHandle {
|
||||
///
|
||||
/// This function will also panic if there is not a currently running future
|
||||
/// task.
|
||||
pub fn schedule_write(&self, tok: usize) {
|
||||
self.send(Message::Schedule(tok, task::park(), Direction::Write));
|
||||
pub fn schedule_write(&self, tok: &IoToken) {
|
||||
self.send(Message::Schedule(tok.token, task::park(), Direction::Write));
|
||||
}
|
||||
|
||||
/// Unregister all information associated with a token on an event loop,
|
||||
@@ -625,13 +612,16 @@ impl LoopHandle {
|
||||
/// ensure that the callbacks are **not** invoked, so pending scheduled
|
||||
/// callbacks cannot be relied upon to get called.
|
||||
///
|
||||
/// > **Note**: This method should generally not be used directly, but
|
||||
/// > rather the `ReadinessStream` type should be used instead.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the event loop this handle is associated
|
||||
/// with has gone away, or if there is an error communicating with the event
|
||||
/// loop.
|
||||
pub fn drop_source(&self, tok: usize) {
|
||||
self.send(Message::DropSource(tok));
|
||||
pub fn drop_source(&self, tok: &IoToken) {
|
||||
self.send(Message::DropSource(tok.token));
|
||||
}
|
||||
|
||||
/// Adds a new timeout to get fired at the specified instant, notifying the
|
||||
@@ -731,16 +721,60 @@ impl LoopPin {
|
||||
///
|
||||
/// Created through the `LoopHandle::add_source` method, this future can also
|
||||
/// resolve to an error if there's an issue communicating with the event loop.
|
||||
pub struct AddSource {
|
||||
inner: LoopFuture<usize, IoSource>,
|
||||
pub struct AddSource<E> {
|
||||
inner: LoopFuture<(E, IoToken), E>,
|
||||
}
|
||||
|
||||
impl Future for AddSource {
|
||||
type Item = usize;
|
||||
/// A token that identifies an active timeout.
|
||||
pub struct IoToken {
|
||||
token: usize,
|
||||
// TODO: can we avoid this allocation? It's kind of a bummer...
|
||||
readiness: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl IoToken {
|
||||
/// Consumes the last readiness notification the token this source is for
|
||||
/// registered.
|
||||
///
|
||||
/// Currently sources receive readiness notifications on an edge-basis. That
|
||||
/// is, once you receive a notification that an object can be read, you
|
||||
/// won't receive any more notifications until all of that data has been
|
||||
/// read.
|
||||
///
|
||||
/// The event loop will fill in this information and then inform futures
|
||||
/// that they're ready to go with the `schedule` method, and then the `poll`
|
||||
/// method can use this to figure out what happened.
|
||||
///
|
||||
/// > **Note**: This method should generally not be used directly, but
|
||||
/// > rather the `ReadinessStream` type should be used instead.
|
||||
// TODO: this should really return a proper newtype/enum, not a usize
|
||||
pub fn take_readiness(&self) -> usize {
|
||||
self.readiness.swap(0, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Future for AddSource<E>
|
||||
where E: mio::Evented + Send + 'static,
|
||||
{
|
||||
type Item = (E, IoToken);
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<usize, io::Error> {
|
||||
self.inner.poll(Loop::add_source, Message::AddSource)
|
||||
fn poll(&mut self) -> Poll<(E, IoToken), io::Error> {
|
||||
let handle = self.inner.loop_handle.clone();
|
||||
self.inner.poll(|lp, io| {
|
||||
let token = try!(lp.add_source(&io));
|
||||
Ok((io, token))
|
||||
}, |io, slot| {
|
||||
Message::Run(Box::new(move || {
|
||||
let res = handle.with_loop(|lp| {
|
||||
let lp = lp.unwrap();
|
||||
let token = try!(lp.add_source(&io));
|
||||
Ok((io, token))
|
||||
});
|
||||
slot.try_produce(res).ok()
|
||||
.expect("add source try_produce intereference");
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,38 +1148,6 @@ impl TimeoutState {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Source<E> {
|
||||
/// Creates a new `Source` wrapping the provided source of events.
|
||||
pub fn new(e: E) -> Source<E> {
|
||||
Source {
|
||||
readiness: AtomicUsize::new(0),
|
||||
io: e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ?Sized> Source<E> {
|
||||
/// Consumes the last readiness notification that this source received.
|
||||
///
|
||||
/// Currently sources receive readiness notifications on an edge-basis. That
|
||||
/// is, once you receive a notification that an object can be read, you
|
||||
/// won't receive any more notifications until all of that data has been
|
||||
/// read.
|
||||
///
|
||||
/// The event loop will fill in this information and then inform futures
|
||||
/// that they're ready to go with the `schedule` method, and then the `poll`
|
||||
/// method can use this to figure out what happened.
|
||||
// TODO: shouldn't return a usize here, but rather some kind of newtype
|
||||
pub fn take_readiness(&self) -> usize {
|
||||
self.readiness.swap(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Gets access to the underlying I/O object.
|
||||
pub fn io(&self) -> &E {
|
||||
&self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor for MioSender {
|
||||
fn execute_boxed(&self, callback: Box<ExecuteCallback>) {
|
||||
self.inner.send(Message::Run(callback))
|
||||
|
||||
Reference in New Issue
Block a user