When Streams are dropped, close Connection (#221) (#222)

When all Streams are dropped / finished, the Connection was held
open until the peer hangs up. Instead, the Connection should hang up
once it knows that nothing more will be sent.

To fix this, we notify the Connection when a stream is no longer
referenced. On the Connection poll(), we check that there are no
active, held, reset streams or any references to the Streams
and transition to sending a GOAWAY if that is case.

The specific behavior depends on if running as a client or server.
This commit is contained in:
Darren Tsung
2018-02-15 13:14:18 -08:00
committed by Carl Lerche
parent 73b4c03b55
commit 0c59957d88
14 changed files with 193 additions and 45 deletions

View File

@@ -13,7 +13,7 @@ fn handshake() {
.write(SETTINGS_ACK)
.build();
let (_, h2) = client::handshake(mock).wait().unwrap();
let (_client, h2) = client::handshake(mock).wait().unwrap();
trace!("hands have been shook");
@@ -129,7 +129,12 @@ fn request_stream_id_overflows() {
let err = client.send_request(request, true).unwrap_err();
assert_eq!(err.to_string(), "user error: stream ID overflowed");
h2.expect("h2")
h2.expect("h2").map(|ret| {
// Hold on to the `client` handle to avoid sending a GO_AWAY
// frame.
drop(client);
ret
})
})
});
@@ -338,7 +343,11 @@ fn http_2_request_without_scheme_or_authority() {
// first request is allowed
assert!(client.send_request(request, true).is_err());
h2.expect("h2")
h2.expect("h2").map(|ret| {
// Hold on to the `client` handle to avoid sending a GO_AWAY frame.
drop(client);
ret
})
});
h2.join(srv).wait().expect("wait");
@@ -614,6 +623,7 @@ fn recv_too_big_headers() {
conn.drive(req1.join(req2))
.and_then(|(conn, _)| conn.expect("client"))
.map(|c| (c, client))
});
client.join(srv).wait().expect("wait");

View File

@@ -161,7 +161,7 @@ fn read_continuation_frames() {
conn.drive(req)
.and_then(move |(h2, _)| {
h2.expect("client")
})
}).map(|c| (client, c))
});
client.join(srv).wait().expect("wait");

View File

@@ -54,6 +54,8 @@ fn write_continuation_frames() {
conn.drive(req)
.and_then(move |(h2, _)| {
h2.unwrap()
}).map(|c| {
(c, client)
})
});

View File

@@ -307,7 +307,9 @@ fn recv_data_overflows_stream_window() {
})
});
conn.unwrap().join(req)
conn.unwrap()
.join(req)
.map(|c| (c, client))
});
h2.join(mock).wait().unwrap();
}
@@ -385,6 +387,7 @@ fn stream_error_release_connection_capacity() {
});
conn.drive(req.expect("response"))
.and_then(|(conn, _)| conn.expect("client"))
.map(|c| (c, client))
});
srv.join(client).wait().unwrap();
@@ -620,19 +623,19 @@ fn reserved_capacity_assigned_in_multi_window_updates() {
h2.drive(
util::wait_for_capacity(stream, 5)
.map(|stream| (response, stream)))
.map(|stream| (response, client, stream)))
})
.and_then(|(h2, (response, mut stream))| {
.and_then(|(h2, (response, client, mut stream))| {
stream.send_data("hello".into(), false).unwrap();
stream.send_data("world".into(), true).unwrap();
h2.drive(response)
h2.drive(response).map(|c| (c, client))
})
.and_then(|(h2, response)| {
.and_then(|((h2, response), client)| {
assert_eq!(response.status(), StatusCode::NO_CONTENT);
// Wait for the connection to close
h2.unwrap()
h2.unwrap().map(|c| (c, client))
});
let srv = srv.assert_client_handshake().unwrap()
@@ -820,20 +823,21 @@ fn recv_settings_removes_available_capacity() {
stream.reserve_capacity(11);
h2.drive(util::wait_for_capacity(stream, 11).map(|s| (response, s)))
h2.drive(util::wait_for_capacity(stream, 11).map(|s| (response, client, s)))
})
.and_then(|(h2, (response, mut stream))| {
.and_then(|(h2, (response, client, mut stream))| {
assert_eq!(stream.capacity(), 11);
stream.send_data("hello world".into(), true).unwrap();
h2.drive(response)
h2.drive(response).map(|c| (c, client))
})
.and_then(|(h2, response)| {
.and_then(|((h2, response), client)| {
assert_eq!(response.status(), StatusCode::NO_CONTENT);
// Wait for the connection to close
h2.unwrap()
// Hold on to the `client` handle to avoid sending a GO_AWAY frame.
h2.unwrap().map(|c| (c, client))
});
let _ = h2.join(srv)
@@ -881,20 +885,21 @@ fn recv_no_init_window_then_receive_some_init_window() {
stream.reserve_capacity(11);
h2.drive(util::wait_for_capacity(stream, 11).map(|s| (response, s)))
h2.drive(util::wait_for_capacity(stream, 11).map(|s| (response, client, s)))
})
.and_then(|(h2, (response, mut stream))| {
.and_then(|(h2, (response, client, mut stream))| {
assert_eq!(stream.capacity(), 11);
stream.send_data("hello world".into(), true).unwrap();
h2.drive(response)
h2.drive(response).map(|c| (c, client))
})
.and_then(|(h2, response)| {
.and_then(|((h2, response), client)| {
assert_eq!(response.status(), StatusCode::NO_CONTENT);
// Wait for the connection to close
h2.unwrap()
// Hold on to the `client` handle to avoid sending a GO_AWAY frame.
h2.unwrap().map(|c| (c, client))
});
let _ = h2.join(srv)
@@ -903,8 +908,8 @@ fn recv_no_init_window_then_receive_some_init_window() {
#[test]
fn settings_lowered_capacity_returns_capacity_to_connection() {
use std::thread;
use std::sync::mpsc;
use std::thread;
let _ = ::env_logger::init();
let (io, srv) = mock::new();
@@ -1038,7 +1043,7 @@ fn client_increase_target_window_size() {
.and_then(|(_client, mut conn)| {
conn.set_target_window_size(2 << 20);
conn.unwrap()
conn.unwrap().map(|c| (c, _client))
});
srv.join(client).wait().unwrap();
@@ -1078,7 +1083,7 @@ fn increase_target_window_size_after_using_some() {
.and_then(|(mut conn, _bytes)| {
conn.set_target_window_size(2 << 20);
conn.unwrap()
})
}).map(|c| (c, client))
});
srv.join(client).wait().unwrap();
@@ -1113,18 +1118,19 @@ fn decrease_target_window_size() {
.uri("https://http2.akamai.com/")
.body(()).unwrap();
let (resp, _) = client.send_request(request, true).unwrap();
conn.drive(resp.expect("response"))
conn.drive(resp.expect("response")).map(|c| (c, client))
})
.and_then(|(mut conn, res)| {
.and_then(|((mut conn, res), client)| {
conn.set_target_window_size(16_384);
let mut body = res.into_parts().1;
let mut cap = body.release_capacity().clone();
conn.drive(body.concat2().expect("concat"))
.and_then(move |(conn, bytes)| {
.map(|c| (c, client))
.and_then(move |((conn, bytes), client)| {
assert_eq!(bytes.len(), 65_535);
cap.release_capacity(bytes.len()).unwrap();
conn.expect("conn")
conn.expect("conn").map(|c| (c, client))
})
});
@@ -1188,10 +1194,10 @@ fn recv_settings_increase_window_size_after_using_some() {
.body(()).unwrap();
let (resp, mut req_body) = client.send_request(request, false).unwrap();
req_body.send_data(vec![0; new_win_size].into(), true).unwrap();
conn.drive(resp.expect("response"))
conn.drive(resp.expect("response")).map(|c| (c, client))
})
.and_then(|(conn, _res)| {
conn.expect("client")
.and_then(|((conn, _res), client)| {
conn.expect("client").map(|c| (c, client))
});
srv.join(client).wait().unwrap();

View File

@@ -10,7 +10,10 @@ fn recv_single_ping() {
// Create the handshake
let h2 = client::handshake(m)
.unwrap()
.and_then(|(_, conn)| conn.unwrap());
.and_then(|(client, conn)| {
conn.unwrap()
.map(|c| (client, c))
});
let mock = mock.assert_client_handshake()
.unwrap()

View File

@@ -204,6 +204,78 @@ fn sends_reset_cancel_when_req_body_is_dropped() {
srv.join(client).wait().expect("wait");
}
#[test]
fn sends_goaway_when_serv_closes_connection() {
let _ = ::env_logger::init();
let (io, client) = mock::new();
let client = client
.assert_server_handshake()
.unwrap()
.recv_settings()
.send_frame(
frames::headers(1)
.request("POST", "https://example.com/")
)
.recv_frame(frames::go_away(1))
.close();
let srv = server::handshake(io).expect("handshake").and_then(|srv| {
srv.into_future().unwrap().and_then(|(_, mut srv)| {
srv.close_connection();
srv.into_future().unwrap()
})
});
srv.join(client).wait().expect("wait");
}
#[test]
fn serve_request_then_serv_closes_connection() {
let _ = ::env_logger::init();
let (io, client) = mock::new();
let client = client
.assert_server_handshake()
.unwrap()
.recv_settings()
.send_frame(
frames::headers(1)
.request("GET", "https://example.com/"),
)
.recv_frame(frames::headers(1).response(200).eos())
.recv_frame(frames::reset(1).cancel())
.send_frame(
frames::headers(3)
.request("GET", "https://example.com/"),
)
.recv_frame(frames::go_away(3))
// streams sent after GOAWAY receive no response
.send_frame(
frames::headers(5)
.request("GET", "https://example.com/"),
)
.close();
let srv = server::handshake(io).expect("handshake").and_then(|srv| {
srv.into_future().unwrap().and_then(|(reqstream, srv)| {
let (req, mut stream) = reqstream.unwrap();
assert_eq!(req.method(), &http::Method::GET);
let rsp = http::Response::builder().status(200).body(()).unwrap();
stream.send_response(rsp, true).unwrap();
srv.into_future().unwrap().and_then(|(_reqstream, mut srv)| {
srv.close_connection();
srv.into_future().unwrap()
})
})
});
srv.join(client).wait().expect("wait");
}
#[test]
fn sends_reset_cancel_when_res_body_is_dropped() {
let _ = ::env_logger::init();

View File

@@ -621,6 +621,7 @@ fn rst_stream_expires() {
err.to_string(),
"protocol error: unspecific protocol error detected"
);
drop(client);
})
});