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:
Alex Crichton
2016-08-20 23:07:00 -07:00
parent 5ab323e5c2
commit b9dae23e3f
5 changed files with 226 additions and 228 deletions
+102 -100
View File
@@ -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))