Simultaneous futures compat (#172)

This patch adds opt-in support for futures 0.2.
This commit is contained in:
Aaron Turon
2018-03-13 13:57:35 -07:00
committed by Carl Lerche
parent 5846b3fc2a
commit d304791c0e
27 changed files with 1045 additions and 105 deletions
+3 -8
View File
@@ -1,10 +1,10 @@
use futures::task::{self, Task};
use std::fmt;
use std::cell::UnsafeCell;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Acquire, Release};
use Task;
/// A synchronization primitive for task notification.
///
/// `AtomicTask` will coordinate concurrent notifications with the consumer
@@ -69,11 +69,6 @@ impl AtomicTask {
}
}
/// Registers the **current** task to be notified on calls to `notify`.
pub fn register(&self) {
self.register_task(task::current());
}
/// Registers the task to be notified on calls to `notify`.
///
/// The new task will take place of any previous tasks that were registered
@@ -89,7 +84,7 @@ impl AtomicTask {
/// idea. Concurrent calls to `register` will attempt to register different
/// tasks to be notified. One of the callers will win and have its task set,
/// but there is no guarantee as to which caller will succeed.
pub fn register_task(&self, task: Task) {
pub(crate) fn register(&self, task: Task) {
match self.state.compare_and_swap(WAITING, LOCKED_WRITE, Acquire) {
WAITING => {
unsafe {
+4 -3
View File
@@ -1,7 +1,7 @@
use {Reactor, Handle};
use {Reactor, Handle, Task};
use atomic_task::AtomicTask;
use futures::{Future, Async, Poll};
use futures::{Future, Async, Poll, task};
use std::io;
use std::thread;
@@ -136,7 +136,8 @@ impl Future for Shutdown {
type Error = ();
fn poll(&mut self) -> Poll<(), ()> {
self.inner.shared.shutdown_task.register();
let task = Task::Futures1(task::current());
self.inner.shared.shutdown_task.register(task);
if !self.inner.is_shutdown() {
return Ok(Async::NotReady);
+39 -2
View File
@@ -39,6 +39,9 @@ extern crate slab;
extern crate tokio_executor;
extern crate tokio_io;
#[cfg(feature = "unstable-futures")]
extern crate futures2;
pub(crate) mod background;
mod atomic_task;
mod poll_evented;
@@ -69,7 +72,6 @@ use std::time::{Duration, Instant};
use log::Level;
use mio::event::Evented;
use slab::Slab;
use futures::task::Task;
/// The core reactor, or event loop.
///
@@ -155,6 +157,14 @@ fn _assert_kinds() {
_assert::<Handle>();
}
/// A wakeup handle for a task, which may be either a futures 0.1 or 0.2 task
#[derive(Debug, Clone)]
pub(crate) enum Task {
Futures1(futures::task::Task),
#[cfg(feature = "unstable-futures")]
Futures2(futures2::task::Waker),
}
// ===== impl Reactor =====
/// Set the default reactor for the duration of the closure
@@ -578,7 +588,7 @@ impl Inner {
Direction::Write => (&sched.writer, mio::Ready::writable()),
};
task.register_task(t);
task.register(t);
if sched.readiness.load(SeqCst) & ready.as_usize() != 0 {
task.notify();
@@ -611,6 +621,17 @@ impl Direction {
}
}
impl Task {
fn notify(&self) {
match *self {
Task::Futures1(ref task) => task.notify(),
#[cfg(feature = "unstable-futures")]
Task::Futures2(ref waker) => waker.wake(),
}
}
}
#[cfg(all(unix, not(target_os = "fuchsia")))]
mod platform {
use mio::Ready;
@@ -637,3 +658,19 @@ mod platform {
false
}
}
#[cfg(feature = "unstable-futures")]
fn lift_async<T>(old: futures::Async<T>) -> futures2::Async<T> {
match old {
futures::Async::Ready(x) => futures2::Async::Ready(x),
futures::Async::NotReady => futures2::Async::Pending,
}
}
#[cfg(feature = "unstable-futures")]
fn lower_async<T>(new: futures2::Async<T>) -> futures::Async<T> {
match new {
futures2::Async::Ready(x) => futures::Async::Ready(x),
futures2::Async::Pending => futures::Async::NotReady,
}
}
+206 -9
View File
@@ -5,6 +5,9 @@ use mio;
use mio::event::Evented;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "unstable-futures")]
use futures2;
use std::fmt;
use std::io::{self, Read, Write};
use std::sync::atomic::AtomicUsize;
@@ -99,7 +102,7 @@ struct Inner {
// ===== impl PollEvented =====
macro_rules! poll_ready {
($me:expr, $mask:expr, $cache:ident, $poll:ident, $take:ident) => {{
($me:expr, $mask:expr, $cache:ident, $take:ident, $poll:expr) => {{
$me.register()?;
// Load cached & encoded readiness.
@@ -114,7 +117,7 @@ macro_rules! poll_ready {
// stream. This happens in a loop to ensure that the stream gets
// drained.
loop {
let ready = try_ready!($me.inner.registration.$poll());
let ready = try_ready!($poll);
cached |= ready.as_usize();
// Update the cache store
@@ -210,7 +213,23 @@ where E: Evented
/// * called from outside of a task context.
pub fn poll_read_ready(&self, mask: mio::Ready) -> Poll<mio::Ready, io::Error> {
assert!(!mask.is_writable(), "cannot poll for write readiness");
poll_ready!(self, mask, read_readiness, poll_read_ready, take_read_ready)
poll_ready!(
self, mask, read_readiness, take_read_ready,
self.inner.registration.poll_read_ready()
)
}
/// Like `poll_read_ready` but compatible with futures 0.2.
#[cfg(feature = "unstable-futures")]
pub fn poll_read_ready2(&self, cx: &mut futures2::task::Context, mask: mio::Ready)
-> futures2::Poll<mio::Ready, io::Error>
{
assert!(!mask.is_writable(), "cannot poll for write readiness");
let mut res = || poll_ready!(
self, mask, read_readiness, take_read_ready,
self.inner.registration.poll_read_ready2(cx).map(::lower_async)
);
res().map(::lift_async)
}
/// Clears the I/O resource's read readiness state and registers the current
@@ -243,6 +262,25 @@ where E: Evented
Ok(())
}
/// Like `clear_read_ready` but compatible with futures 0.2.
#[cfg(feature = "unstable-futures")]
pub fn clear_read_ready2(&self, cx: &mut futures2::task::Context, ready: mio::Ready)
-> io::Result<()>
{
// Cannot clear write readiness
assert!(!ready.is_writable(), "cannot clear write readiness");
assert!(!::platform::is_hup(&ready), "cannot clear HUP readiness");
self.inner.read_readiness.fetch_and(!ready.as_usize(), Relaxed);
if self.poll_read_ready2(cx, ready)?.is_ready() {
// Notify the current task
cx.waker().wake()
}
Ok(())
}
/// Check the I/O resource's write readiness state.
///
/// This always checks for writable readiness and also checks for HUP
@@ -263,13 +301,31 @@ where E: Evented
/// * `ready` contains bits besides `writable` and `hup`.
/// * called from outside of a task context.
pub fn poll_write_ready(&self) -> Poll<mio::Ready, io::Error> {
poll_ready!(self,
mio::Ready::writable(),
write_readiness,
poll_write_ready,
take_write_ready)
poll_ready!(
self,
mio::Ready::writable(),
write_readiness,
take_write_ready,
self.inner.registration.poll_write_ready()
)
}
/// Like `poll_write_ready` but compatible with futures 0.2.
#[cfg(feature = "unstable-futures")]
pub fn poll_write_ready2(&self, cx: &mut futures2::task::Context)
-> futures2::Poll<mio::Ready, io::Error>
{
let mut res = || poll_ready!(
self,
mio::Ready::writable(),
write_readiness,
take_write_ready,
self.inner.registration.poll_write_ready2(cx).map(::lower_async)
);
res().map(::lift_async)
}
/// Resets the I/O resource's write readiness state and registers the current
/// task to be notified once a write readiness event is received.
///
@@ -295,6 +351,21 @@ where E: Evented
Ok(())
}
/// Like `clear_write_ready`, but compatible with futures 0.2.
#[cfg(feature = "unstable-futures")]
pub fn clear_write_ready2(&self, cx: &mut futures2::task::Context) -> io::Result<()> {
let ready = mio::Ready::writable();
self.inner.read_readiness.fetch_and(!ready.as_usize(), Relaxed);
if self.poll_write_ready2(cx)?.is_ready() {
// Notify the current task
cx.waker().wake()
}
Ok(())
}
/// Ensure that the I/O resource is registered with the reactor.
fn register(&self) -> io::Result<()> {
self.inner.registration.register(self.io.as_ref().unwrap())?;
@@ -322,6 +393,28 @@ where E: Evented + Read,
}
}
#[cfg(feature = "unstable-futures")]
impl<E> futures2::io::AsyncRead for PollEvented<E>
where E: Evented, E: Read,
{
fn poll_read(&mut self, cx: &mut futures2::task::Context, buf: &mut [u8])
-> futures2::Poll<usize, io::Error>
{
if let futures2::Async::Pending = self.poll_read_ready2(cx, mio::Ready::readable())? {
return Ok(futures2::Async::Pending);
}
match self.get_mut().read(buf) {
Ok(n) => Ok(futures2::Async::Ready(n)),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_read_ready2(cx, mio::Ready::readable())?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
}
impl<E> Write for PollEvented<E>
where E: Evented + Write,
{
@@ -354,6 +447,48 @@ where E: Evented + Write,
}
}
#[cfg(feature = "unstable-futures")]
impl<E> futures2::io::AsyncWrite for PollEvented<E>
where E: Evented, E: Write,
{
fn poll_write(&mut self, cx: &mut futures2::task::Context, buf: &[u8])
-> futures2::Poll<usize, io::Error>
{
if let futures2::Async::Pending = self.poll_write_ready2(cx)? {
return Ok(futures2::Async::Pending);
}
match self.get_mut().write(buf) {
Ok(n) => Ok(futures2::Async::Ready(n)),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_write_ready2(cx)?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
fn poll_flush(&mut self, cx: &mut futures2::task::Context) -> futures2::Poll<(), io::Error> {
if let futures2::Async::Pending = self.poll_write_ready2(cx)? {
return Ok(futures2::Async::Pending);
}
match self.get_mut().flush() {
Ok(_) => Ok(futures2::Async::Ready(())),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_write_ready2(cx)?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
fn poll_close(&mut self, cx: &mut futures2::task::Context) -> futures2::Poll<(), io::Error> {
futures2::io::AsyncWrite::poll_flush(self, cx)
}
}
impl<E> AsyncRead for PollEvented<E>
where E: Evented + Read,
{
@@ -387,6 +522,28 @@ where E: Evented, &'a E: Read,
}
}
#[cfg(feature = "unstable-futures")]
impl<'a, E> futures2::io::AsyncRead for &'a PollEvented<E>
where E: Evented, &'a E: Read,
{
fn poll_read(&mut self, cx: &mut futures2::task::Context, buf: &mut [u8])
-> futures2::Poll<usize, io::Error>
{
if let futures2::Async::Pending = self.poll_read_ready2(cx, mio::Ready::readable())? {
return Ok(futures2::Async::Pending);
}
match self.get_ref().read(buf) {
Ok(n) => Ok(futures2::Async::Ready(n)),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_read_ready2(cx, mio::Ready::readable())?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
}
impl<'a, E> Write for &'a PollEvented<E>
where E: Evented, &'a E: Write,
{
@@ -419,6 +576,47 @@ where E: Evented, &'a E: Write,
}
}
#[cfg(feature = "unstable-futures")]
impl<'a, E> futures2::io::AsyncWrite for &'a PollEvented<E>
where E: Evented, &'a E: Write,
{
fn poll_write(&mut self, cx: &mut futures2::task::Context, buf: &[u8])
-> futures2::Poll<usize, io::Error>
{
if let futures2::Async::Pending = self.poll_write_ready2(cx)? {
return Ok(futures2::Async::Pending);
}
match self.get_ref().write(buf) {
Ok(n) => Ok(futures2::Async::Ready(n)),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_write_ready2(cx)?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
fn poll_flush(&mut self, cx: &mut futures2::task::Context) -> futures2::Poll<(), io::Error> {
if let futures2::Async::Pending = self.poll_write_ready2(cx)? {
return Ok(futures2::Async::Pending);
}
match self.get_ref().flush() {
Ok(_) => Ok(futures2::Async::Ready(())),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.clear_write_ready2(cx)?;
Ok(futures2::Async::Pending)
}
Err(e) => Err(e),
}
}
fn poll_close(&mut self, cx: &mut futures2::task::Context) -> futures2::Poll<(), io::Error> {
futures2::io::AsyncWrite::poll_flush(self, cx)
}
}
impl<'a, E> AsyncRead for &'a PollEvented<E>
where E: Evented, &'a E: Read,
{
@@ -439,7 +637,6 @@ fn is_wouldblock<T>(r: &io::Result<T>) -> bool {
}
}
impl<E: Evented + fmt::Debug> fmt::Debug for PollEvented<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PollEvented")
+43 -13
View File
@@ -1,9 +1,11 @@
use {Handle, Direction};
use {Handle, Direction, Task};
use futures::{Async, Poll};
use futures::task::{self, Task};
use futures::{Async, Poll, task};
use mio::{self, Evented};
#[cfg(feature = "unstable-futures")]
use futures2;
use std::{io, mem, usize};
use std::cell::UnsafeCell;
use std::sync::atomic::AtomicUsize;
@@ -271,13 +273,26 @@ impl Registration {
///
/// This function will panic if called from outside of a task context.
pub fn poll_read_ready(&self) -> Poll<mio::Ready, io::Error> {
self.poll_ready(Direction::Read, true)
self.poll_ready(Direction::Read, true, || Task::Futures1(task::current()))
.map(|v| match v {
Some(v) => Async::Ready(v),
_ => Async::NotReady,
})
}
/// Like `poll_ready_ready`, but compatible with futures 0.2
#[cfg(feature = "unstable-futures")]
pub fn poll_read_ready2(&self, cx: &mut futures2::task::Context)
-> futures2::Poll<mio::Ready, io::Error>
{
use futures2::Async as Async2;
self.poll_ready(Direction::Read, true, || Task::Futures2(cx.waker().clone()))
.map(|v| match v {
Some(v) => Async2::Ready(v),
_ => Async2::Pending,
})
}
/// Consume any pending read readiness event.
///
/// This function is identical to [`poll_read_ready`] **except** that it
@@ -286,7 +301,7 @@ impl Registration {
///
/// [`poll_read_ready`]: #method.poll_read_ready
pub fn take_read_ready(&self) -> io::Result<Option<mio::Ready>> {
self.poll_ready(Direction::Read, false)
self.poll_ready(Direction::Read, false, || panic!())
}
@@ -323,13 +338,26 @@ impl Registration {
///
/// This function will panic if called from outside of a task context.
pub fn poll_write_ready(&self) -> Poll<mio::Ready, io::Error> {
self.poll_ready(Direction::Write, true)
self.poll_ready(Direction::Write, true, || Task::Futures1(task::current()))
.map(|v| match v {
Some(v) => Async::Ready(v),
_ => Async::NotReady,
})
}
/// Like `poll_write_ready`, but compatible with futures 0.2
#[cfg(feature = "unstable-futures")]
pub fn poll_write_ready2(&self, cx: &mut futures2::task::Context)
-> futures2::Poll<mio::Ready, io::Error>
{
use futures2::Async as Async2;
self.poll_ready(Direction::Write, true, || Task::Futures2(cx.waker().clone()))
.map(|v| match v {
Some(v) => Async2::Ready(v),
_ => Async2::Pending,
})
}
/// Consume any pending write readiness event.
///
/// This function is identical to [`poll_write_ready`] **except** that it
@@ -338,11 +366,12 @@ impl Registration {
///
/// [`poll_write_ready`]: #method.poll_write_ready
pub fn take_write_ready(&self) -> io::Result<Option<mio::Ready>> {
self.poll_ready(Direction::Write, false)
self.poll_ready(Direction::Write, false, || unreachable!())
}
fn poll_ready(&self, direction: Direction, notify: bool)
fn poll_ready<F>(&self, direction: Direction, notify: bool, task: F)
-> io::Result<Option<mio::Ready>>
where F: Fn() -> Task
{
let mut state = self.state.load(SeqCst);
@@ -357,7 +386,7 @@ impl Registration {
}
READY => {
let inner = unsafe { (*self.inner.get()).as_ref().unwrap() };
return inner.poll_ready(direction, notify);
return inner.poll_ready(direction, notify, task);
}
_ => {
if !notify {
@@ -371,7 +400,7 @@ impl Registration {
let mut n = node.take().unwrap_or_else(|| {
Box::new(Node {
direction,
task: task::current(),
task: task(),
next: None,
})
});
@@ -472,8 +501,9 @@ impl Inner {
inner.deregister_source(io)
}
fn poll_ready(&self, direction: Direction, notify: bool)
fn poll_ready<F>(&self, direction: Direction, notify: bool, task: F)
-> io::Result<Option<mio::Ready>>
where F: FnOnce() -> Task
{
if self.token == ERROR {
return Err(io::Error::new(io::ErrorKind::Other, "failed to associate with reactor"));
@@ -504,8 +534,8 @@ impl Inner {
if ready.is_empty() && notify {
// Update the task info
match direction {
Direction::Read => sched.reader.register(),
Direction::Write => sched.writer.register(),
Direction::Read => sched.reader.register(task()),
Direction::Write => sched.writer.register(task()),
}
// Try again