Add user PING support (#346)

- Add `share::PingPong`, which can send `Ping`s, and poll for the `Pong`
  from the peer.
This commit is contained in:
Sean McArthur
2019-02-18 15:59:11 -08:00
committed by GitHub
parent 8a0b7ff64f
commit e3a73f726e
12 changed files with 458 additions and 31 deletions

View File

@@ -38,6 +38,7 @@ pub use self::bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
pub use tokio_io::{AsyncRead, AsyncWrite};
pub use std::thread;
pub use std::time::Duration;
// ===== Everything under here shouldn't be used =====

View File

@@ -8,6 +8,19 @@ pub fn byte_str(s: &str) -> String<Bytes> {
String::try_from(Bytes::from(s)).unwrap()
}
pub fn yield_once() -> impl Future<Item=(), Error=()> {
let mut yielded = false;
futures::future::poll_fn(move || {
if yielded {
Ok(Async::Ready(()))
} else {
yielded = true;
futures::task::current().notify();
Ok(Async::NotReady)
}
})
}
pub fn wait_for_capacity(stream: h2::SendStream<Bytes>, target: usize) -> WaitForCapacity {
WaitForCapacity {
stream: Some(stream),

View File

@@ -108,3 +108,100 @@ fn pong_has_highest_priority() {
srv.join(client).wait().expect("wait");
}
#[test]
fn user_ping_pong() {
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
let srv = srv.assert_client_handshake()
.expect("srv handshake")
.recv_settings()
.recv_frame(frames::ping(frame::Ping::USER))
.send_frame(frames::ping(frame::Ping::USER).pong())
.recv_frame(frames::go_away(0))
.recv_eof();
let client = client::handshake(io)
.expect("client handshake")
.and_then(|(client, conn)| {
// yield once so we can ack server settings
conn
.drive(util::yield_once())
.map(move |(conn, ())| (client, conn))
})
.and_then(|(client, mut conn)| {
// `ping_pong()` method conflict with mock future ext trait.
let mut ping_pong = client::Connection::ping_pong(&mut conn)
.expect("taking ping_pong");
ping_pong
.send_ping(Ping::opaque())
.expect("send ping");
// multiple pings results in a user error...
assert_eq!(
ping_pong.send_ping(Ping::opaque()).expect_err("ping 2").to_string(),
"user error: send_ping before received previous pong",
"send_ping while ping pending is a user error",
);
conn
.drive(futures::future::poll_fn(move || {
ping_pong.poll_pong()
}))
.and_then(move |(conn, _pong)| {
drop(client);
conn.expect("client")
})
});
client.join(srv).wait().expect("wait");
}
#[test]
fn user_notifies_when_connection_closes() {
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
let srv = srv.assert_client_handshake()
.expect("srv handshake")
.recv_settings();
let client = client::handshake(io)
.expect("client handshake")
.and_then(|(client, conn)| {
// yield once so we can ack server settings
conn
.drive(util::yield_once())
.map(move |(conn, ())| (client, conn))
})
.map(|(_client, conn)| conn);
let (mut client, srv) = client.join(srv).wait().expect("wait");
// `ping_pong()` method conflict with mock future ext trait.
let mut ping_pong = client::Connection::ping_pong(&mut client)
.expect("taking ping_pong");
// Spawn a thread so we can park a task waiting on `poll_pong`, and then
// drop the client and be sure the parked task is notified...
let t = thread::spawn(move || {
poll_fn(|| { ping_pong.poll_pong() })
.wait()
.expect_err("poll_pong should error");
ping_pong
});
// Sleep to let the ping thread park its task...
thread::sleep(Duration::from_millis(50));
drop(client);
drop(srv);
let mut ping_pong = t.join().expect("ping pong thread join");
// Now that the connection is closed, also test `send_ping` errors...
assert_eq!(
ping_pong.send_ping(Ping::opaque()).expect_err("send_ping").to_string(),
"broken pipe",
);
}