Fix some flow control bugs. (#152)
* Release stream capacity back to the connection to avoid capacity leaks. * Actually notify waiting tasks when capacity becomes available.
This commit is contained in:
@@ -248,6 +248,8 @@ where
|
||||
where
|
||||
R: Resolve<B, P>,
|
||||
{
|
||||
trace!("assign_connection_capacity; inc={}", inc);
|
||||
|
||||
self.flow.assign_capacity(inc);
|
||||
|
||||
// Assign newly acquired capacity to streams pending capacity.
|
||||
@@ -315,6 +317,8 @@ where
|
||||
// TODO: Should prioritization factor into this?
|
||||
let assign = cmp::min(conn_available, additional);
|
||||
|
||||
trace!(" assigning; num={}", assign);
|
||||
|
||||
// Assign the capacity to the stream
|
||||
stream.assign_capacity(assign);
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ where
|
||||
}
|
||||
|
||||
if !stream.send_capacity_inc {
|
||||
stream.wait_send();
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
|
||||
@@ -304,13 +305,16 @@ where
|
||||
|
||||
trace!("decrementing all windows; dec={}", dec);
|
||||
|
||||
let mut total_reclaimed = 0;
|
||||
store.for_each(|mut stream| {
|
||||
let stream = &mut *stream;
|
||||
|
||||
stream.send_flow.dec_window(dec);
|
||||
|
||||
let available = stream.send_flow.available().as_size();
|
||||
stream.send_flow.claim_capacity(cmp::min(dec, available));
|
||||
let reclaim = cmp::min(dec, available);
|
||||
stream.send_flow.claim_capacity(reclaim);
|
||||
total_reclaimed += reclaim;
|
||||
|
||||
trace!(
|
||||
"decremented stream window; id={:?}; decr={}; flow={:?}",
|
||||
@@ -319,15 +323,15 @@ where
|
||||
stream.send_flow
|
||||
);
|
||||
|
||||
// TODO: Probably try to assign capacity?
|
||||
|
||||
// TODO: Handle reclaiming connection level window
|
||||
// capacity.
|
||||
|
||||
// TODO: Should this notify the producer?
|
||||
// TODO: Should this notify the producer when the capacity
|
||||
// of a stream is reduced? Maybe it should if the capacity
|
||||
// is reduced to zero, allowing the producer to stop work.
|
||||
|
||||
Ok::<_, RecvError>(())
|
||||
})?;
|
||||
|
||||
self.prioritize
|
||||
.assign_connection_capacity(total_reclaimed, store);
|
||||
} else if val > old_val {
|
||||
let inc = val - old_val;
|
||||
|
||||
|
||||
@@ -229,8 +229,12 @@ where
|
||||
self.send_capacity_inc = true;
|
||||
self.send_flow.assign_capacity(capacity);
|
||||
|
||||
trace!(" assigned capacity to stream; available={}; buffered={}; id={:?}",
|
||||
self.send_flow.available(), self.buffered_send_data, self.id);
|
||||
|
||||
// Only notify if the capacity exceeds the amount of buffered data
|
||||
if self.send_flow.available() > self.buffered_send_data {
|
||||
trace!(" notifying task");
|
||||
self.notify_send();
|
||||
}
|
||||
}
|
||||
@@ -263,6 +267,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_send(&mut self) {
|
||||
self.send_task = Some(task::current());
|
||||
}
|
||||
|
||||
pub fn notify_recv(&mut self) {
|
||||
if let Some(task) = self.recv_task.take() {
|
||||
task.notify();
|
||||
|
||||
@@ -801,6 +801,170 @@ fn recv_settings_removes_available_capacity() {
|
||||
.wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_no_init_window_then_receive_some_init_window() {
|
||||
let _ = ::env_logger::init();
|
||||
let (io, srv) = mock::new();
|
||||
|
||||
let mut settings = frame::Settings::default();
|
||||
settings.set_initial_window_size(Some(0));
|
||||
|
||||
let srv = srv.assert_client_handshake_with_settings(settings).unwrap()
|
||||
.recv_settings()
|
||||
.recv_frame(
|
||||
frames::headers(1)
|
||||
.request("POST", "https://http2.akamai.com/")
|
||||
)
|
||||
.idle_ms(100)
|
||||
.send_frame(frames::settings().initial_window_size(10))
|
||||
.recv_frame(frames::settings_ack())
|
||||
.recv_frame(frames::data(1, "hello worl"))
|
||||
.idle_ms(100)
|
||||
.send_frame(frames::settings().initial_window_size(11))
|
||||
.recv_frame(frames::settings_ack())
|
||||
.recv_frame(frames::data(1, "d").eos())
|
||||
.send_frame(
|
||||
frames::headers(1)
|
||||
.response(204)
|
||||
.eos()
|
||||
)
|
||||
.close();
|
||||
|
||||
|
||||
let h2 = Client::handshake(io).unwrap()
|
||||
.and_then(|(mut client, h2)| {
|
||||
let request = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri("https://http2.akamai.com/")
|
||||
.body(()).unwrap();
|
||||
|
||||
let mut stream = client.send_request(request, false).unwrap();
|
||||
|
||||
stream.reserve_capacity(11);
|
||||
|
||||
h2.drive(util::wait_for_capacity(stream, 11))
|
||||
})
|
||||
.and_then(|(h2, mut stream)| {
|
||||
assert_eq!(stream.capacity(), 11);
|
||||
|
||||
stream.send_data("hello world".into(), true).unwrap();
|
||||
|
||||
h2.drive(GetResponse { stream: Some(stream) })
|
||||
})
|
||||
.and_then(|(h2, (response, _))| {
|
||||
assert_eq!(response.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
// Wait for the connection to close
|
||||
h2.unwrap()
|
||||
});
|
||||
|
||||
let _ = h2.join(srv)
|
||||
.wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_lowered_capacity_returns_capacity_to_connection() {
|
||||
use std::thread;
|
||||
|
||||
let _ = ::env_logger::init();
|
||||
let (io, srv) = mock::new();
|
||||
|
||||
let window_size = frame::DEFAULT_INITIAL_WINDOW_SIZE as usize;
|
||||
|
||||
// Spawn the server on a thread
|
||||
let th1 = thread::spawn(move || {
|
||||
srv.assert_client_handshake().unwrap()
|
||||
.recv_settings()
|
||||
.recv_frame(
|
||||
frames::headers(1)
|
||||
.request("POST", "https://example.com/one")
|
||||
)
|
||||
.recv_frame(
|
||||
frames::headers(3)
|
||||
.request("POST", "https://example.com/two")
|
||||
)
|
||||
.idle_ms(200)
|
||||
// Remove all capacity from streams
|
||||
.send_frame(frames::settings().initial_window_size(0))
|
||||
.recv_frame(frames::settings_ack())
|
||||
|
||||
// Let stream 3 make progress
|
||||
.send_frame(frames::window_update(3, 11))
|
||||
.recv_frame(frames::data(3, "hello world").eos())
|
||||
|
||||
// Wait a bit
|
||||
//
|
||||
// TODO: Receive signal from main thread
|
||||
.idle_ms(200)
|
||||
|
||||
// Reset initial window size
|
||||
.send_frame(frames::settings().initial_window_size(window_size as u32))
|
||||
.recv_frame(frames::settings_ack())
|
||||
|
||||
// Get data from first stream
|
||||
.recv_frame(frames::data(1, "hello world").eos())
|
||||
|
||||
// Send responses
|
||||
.send_frame(
|
||||
frames::headers(1)
|
||||
.response(204)
|
||||
.eos()
|
||||
)
|
||||
.send_frame(
|
||||
frames::headers(3)
|
||||
.response(204)
|
||||
.eos()
|
||||
)
|
||||
.close()
|
||||
.wait().unwrap();
|
||||
});
|
||||
|
||||
let (mut client, h2) = Client::handshake(io).unwrap()
|
||||
.wait().unwrap();
|
||||
|
||||
// Drive client connection
|
||||
let th2 = thread::spawn(move || {
|
||||
h2.wait().unwrap();
|
||||
});
|
||||
|
||||
let request = Request::post("https://example.com/one")
|
||||
.body(()).unwrap();
|
||||
|
||||
let mut stream1 = client.send_request(request, false).unwrap();
|
||||
|
||||
let request = Request::post("https://example.com/two")
|
||||
.body(()).unwrap();
|
||||
|
||||
let mut stream2 = client.send_request(request, false).unwrap();
|
||||
|
||||
// Reserve capacity for stream one, this will consume all connection level
|
||||
// capacity
|
||||
stream1.reserve_capacity(window_size);
|
||||
let stream1 = util::wait_for_capacity(stream1, window_size).wait().unwrap();
|
||||
|
||||
// Now, wait for capacity on the other stream
|
||||
stream2.reserve_capacity(11);
|
||||
let mut stream2 = util::wait_for_capacity(stream2, 11).wait().unwrap();
|
||||
|
||||
// Send data on stream 2
|
||||
stream2.send_data("hello world".into(), true).unwrap();
|
||||
|
||||
// Wait for capacity on stream 1
|
||||
let mut stream1 = util::wait_for_capacity(stream1, 11).wait().unwrap();
|
||||
|
||||
stream1.send_data("hello world".into(), true).unwrap();
|
||||
|
||||
// Wait for responses..
|
||||
let resp = stream1.wait().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
let resp = stream2.wait().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
th1.join().unwrap();
|
||||
th2.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_increase_target_window_size() {
|
||||
let _ = ::env_logger::init();
|
||||
|
||||
@@ -68,6 +68,10 @@ pub fn settings() -> Mock<frame::Settings> {
|
||||
Mock(frame::Settings::default())
|
||||
}
|
||||
|
||||
pub fn settings_ack() -> Mock<frame::Settings> {
|
||||
Mock(frame::Settings::ack())
|
||||
}
|
||||
|
||||
pub fn ping(payload: [u8; 8]) -> Mock<frame::Ping> {
|
||||
Mock(frame::Ping::new(payload))
|
||||
}
|
||||
@@ -269,6 +273,11 @@ impl Mock<frame::Settings> {
|
||||
self.0.set_max_concurrent_streams(Some(max));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn initial_window_size(mut self, val: u32) -> Self {
|
||||
self.0.set_initial_window_size(Some(val));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mock<frame::Settings>> for frame::Settings {
|
||||
|
||||
@@ -35,8 +35,6 @@ impl Future for WaitForCapacity {
|
||||
|
||||
let act = self.stream().capacity();
|
||||
|
||||
println!("CAP={:?}", act);
|
||||
|
||||
if act >= self.target {
|
||||
return Ok(self.stream.take().unwrap().into());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user