Skip to content

Commit 13741f5

Browse files
committed
fix(client): never call connect if idle connection is available
The HttpConnector's connect future was lazy, but if any custom connector did not use a lazy future, then a connect would always be started, even if an idle connection was available.
1 parent ad77630 commit 13741f5

File tree

2 files changed

+105
-27
lines changed

2 files changed

+105
-27
lines changed

src/client/mod.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,22 +182,25 @@ where C: Connect,
182182
let pool = self.pool.clone();
183183
let pool_key = Rc::new(domain.to_string());
184184
let h1_writev = self.h1_writev;
185-
self.connector.connect(url)
186-
.and_then(move |io| {
187-
let (tx, rx) = dispatch::channel();
188-
let tx = HyperClient {
189-
tx: tx,
190-
should_close: Cell::new(true),
191-
};
192-
let pooled = pool.pooled(pool_key, tx);
193-
let mut conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone());
194-
if !h1_writev {
195-
conn.set_write_strategy_flatten();
196-
}
197-
let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn);
198-
executor.execute(dispatch.map_err(|e| debug!("client connection error: {}", e)))?;
199-
Ok(pooled)
200-
})
185+
let connector = self.connector.clone();
186+
future::lazy(move || {
187+
connector.connect(url)
188+
.and_then(move |io| {
189+
let (tx, rx) = dispatch::channel();
190+
let tx = HyperClient {
191+
tx: tx,
192+
should_close: Cell::new(true),
193+
};
194+
let pooled = pool.pooled(pool_key, tx);
195+
let mut conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone());
196+
if !h1_writev {
197+
conn.set_write_strategy_flatten();
198+
}
199+
let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn);
200+
executor.execute(dispatch.map_err(|e| debug!("client connection error: {}", e)))?;
201+
Ok(pooled)
202+
})
203+
})
201204
};
202205

203206
let race = checkout.select(connect)

tests/client.rs

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ mod dispatch_impl {
725725
let handle = core.handle();
726726
let closes = Arc::new(AtomicUsize::new(0));
727727
let client = Client::configure()
728-
.connector(DebugConnector(HttpConnector::new(1, &core.handle()), closes.clone()))
728+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &core.handle()), closes.clone()))
729729
.build(&handle);
730730

731731
let (tx1, rx1) = oneshot::channel();
@@ -784,7 +784,7 @@ mod dispatch_impl {
784784

785785
let res = {
786786
let client = Client::configure()
787-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
787+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
788788
.build(&handle);
789789
client.get(uri).and_then(move |res| {
790790
assert_eq!(res.status(), hyper::StatusCode::Ok);
@@ -834,7 +834,7 @@ mod dispatch_impl {
834834
let uri = format!("http://{}/a", addr).parse().unwrap();
835835

836836
let client = Client::configure()
837-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
837+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
838838
.build(&handle);
839839
let res = client.get(uri).and_then(move |res| {
840840
assert_eq!(res.status(), hyper::StatusCode::Ok);
@@ -883,7 +883,7 @@ mod dispatch_impl {
883883

884884
let res = {
885885
let client = Client::configure()
886-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
886+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
887887
.build(&handle);
888888
client.get(uri)
889889
};
@@ -927,7 +927,7 @@ mod dispatch_impl {
927927

928928
let res = {
929929
let client = Client::configure()
930-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
930+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
931931
.build(&handle);
932932
// notably, havent read body yet
933933
client.get(uri)
@@ -966,7 +966,7 @@ mod dispatch_impl {
966966
let uri = format!("http://{}/a", addr).parse().unwrap();
967967

968968
let client = Client::configure()
969-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
969+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
970970
.keep_alive(false)
971971
.build(&handle);
972972
let res = client.get(uri).and_then(move |res| {
@@ -1005,7 +1005,7 @@ mod dispatch_impl {
10051005
let uri = format!("http://{}/a", addr).parse().unwrap();
10061006

10071007
let client = Client::configure()
1008-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
1008+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
10091009
.build(&handle);
10101010
let res = client.get(uri).and_then(move |res| {
10111011
assert_eq!(res.status(), hyper::StatusCode::Ok);
@@ -1095,7 +1095,7 @@ mod dispatch_impl {
10951095
let uri = format!("http://{}/a", addr).parse().unwrap();
10961096

10971097
let client = Client::configure()
1098-
.connector(DebugConnector(HttpConnector::new(1, &handle), closes.clone()))
1098+
.connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes.clone()))
10991099
.executor(handle.clone());
11001100
let res = client.get(uri).and_then(move |res| {
11011101
assert_eq!(res.status(), hyper::StatusCode::Ok);
@@ -1110,7 +1110,79 @@ mod dispatch_impl {
11101110
assert_eq!(closes.load(Ordering::Relaxed), 1);
11111111
}
11121112

1113-
struct DebugConnector(HttpConnector, Arc<AtomicUsize>);
1113+
#[test]
1114+
fn idle_conn_prevents_connect_call() {
1115+
let _ = pretty_env_logger::try_init();
1116+
1117+
let server = TcpListener::bind("127.0.0.1:0").unwrap();
1118+
let addr = server.local_addr().unwrap();
1119+
let mut core = Core::new().unwrap();
1120+
let handle = core.handle();
1121+
let connector = DebugConnector::new(&handle);
1122+
let connects = connector.connects.clone();
1123+
1124+
let (tx1, rx1) = oneshot::channel();
1125+
let (tx2, rx2) = oneshot::channel();
1126+
1127+
thread::spawn(move || {
1128+
let mut sock = server.accept().unwrap().0;
1129+
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
1130+
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
1131+
let mut buf = [0; 4096];
1132+
sock.read(&mut buf).expect("read 1");
1133+
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap();
1134+
let _ = tx1.send(());
1135+
1136+
sock.read(&mut buf).expect("read 2");
1137+
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap();
1138+
let _ = tx2.send(());
1139+
});
1140+
1141+
let uri: hyper::Uri = format!("http://{}/a", addr).parse().unwrap();
1142+
1143+
let client = Client::configure()
1144+
.connector(connector)
1145+
.build(&handle);
1146+
1147+
let res = client.get(uri.clone()).and_then(move |res| {
1148+
assert_eq!(res.status(), hyper::StatusCode::Ok);
1149+
res.body().concat2()
1150+
});
1151+
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
1152+
core.run(res.join(rx).map(|r| r.0)).unwrap();
1153+
assert_eq!(connects.load(Ordering::Relaxed), 1);
1154+
1155+
let res2 = client.get(uri).and_then(move |res| {
1156+
assert_eq!(res.status(), hyper::StatusCode::Ok);
1157+
res.body().concat2()
1158+
});
1159+
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
1160+
core.run(res2.join(rx).map(|r| r.0)).unwrap();
1161+
1162+
assert_eq!(connects.load(Ordering::Relaxed), 1);
1163+
}
1164+
1165+
1166+
struct DebugConnector {
1167+
http: HttpConnector,
1168+
closes: Arc<AtomicUsize>,
1169+
connects: Arc<AtomicUsize>,
1170+
}
1171+
1172+
impl DebugConnector {
1173+
fn new(handle: &Handle) -> DebugConnector {
1174+
let http = HttpConnector::new(1, handle);
1175+
DebugConnector::with_http_and_closes(http, Arc::new(AtomicUsize::new(0)))
1176+
}
1177+
1178+
fn with_http_and_closes(http: HttpConnector, closes: Arc<AtomicUsize>) -> DebugConnector {
1179+
DebugConnector {
1180+
http: http,
1181+
closes: closes,
1182+
connects: Arc::new(AtomicUsize::new(0)),
1183+
}
1184+
}
1185+
}
11141186

11151187
impl Service for DebugConnector {
11161188
type Request = Uri;
@@ -1119,8 +1191,11 @@ mod dispatch_impl {
11191191
type Future = Box<Future<Item = DebugStream, Error = io::Error>>;
11201192

11211193
fn call(&self, uri: Uri) -> Self::Future {
1122-
let counter = self.1.clone();
1123-
Box::new(self.0.call(uri).map(move |s| DebugStream(s, counter)))
1194+
self.connects.fetch_add(1, Ordering::SeqCst);
1195+
let closes = self.closes.clone();
1196+
Box::new(self.http.call(uri).map(move |s| {
1197+
DebugStream(s, closes)
1198+
}))
11241199
}
11251200
}
11261201

0 commit comments

Comments
 (0)