22
33use std:: borrow:: Cow ;
44use std:: collections:: HashMap ;
5+ use std:: future:: Future ;
56use std:: net:: SocketAddr ;
67use std:: sync:: { Arc , Mutex } ;
8+ use std:: time:: Duration ;
79
810use tokio:: net:: { ToSocketAddrs , lookup_host} ;
911
1012use crate :: error:: { ClientError , MemcacheError , ServerError } ;
1113
1214use super :: async_connection:: AsyncMetaConnection ;
13- use super :: client:: { DEFAULT_MAX_IDLE , default_hash_function, jump_hash} ;
15+ use super :: client:: { DEFAULT_MAX_IDLE , Timeouts , default_hash_function, jump_hash} ;
1416use super :: core:: { self , Operation } ;
1517use super :: meta_api:: { ArithmeticMode , build_debug, build_noop, parse_debug_result, parse_meta_result} ;
1618use super :: meta_command:: { MetaCommand , ReturnCode } ;
@@ -19,6 +21,22 @@ use super::request::Request;
1921use super :: result:: OpResult ;
2022use super :: value:: ToValue ;
2123
24+ /// Bound a transport future by a timeout; `None` means unbounded. A
25+ /// timeout surfaces as an io error and so poisons the connection like any
26+ /// other transport failure.
27+ async fn timed < T > (
28+ timeout : Option < Duration > ,
29+ future : impl Future < Output = Result < T , MemcacheError > > ,
30+ ) -> Result < T , MemcacheError > {
31+ match timeout {
32+ Some ( duration) => match tokio:: time:: timeout ( duration, future) . await {
33+ Ok ( result) => result,
34+ Err ( _) => Err ( std:: io:: Error :: from ( std:: io:: ErrorKind :: TimedOut ) . into ( ) ) ,
35+ } ,
36+ None => future. await ,
37+ }
38+ }
39+
2240/// One server: its resolved addresses and a stack of idle connections.
2341/// The mutex is only held to pop/push, never across I/O.
2442struct AsyncServer {
@@ -27,12 +45,12 @@ struct AsyncServer {
2745}
2846
2947impl AsyncServer {
30- async fn checkout ( & self ) -> Result < AsyncMetaConnection , MemcacheError > {
48+ async fn checkout ( & self , timeouts : & Timeouts ) -> Result < AsyncMetaConnection , MemcacheError > {
3149 let reused = self . idle . lock ( ) . unwrap ( ) . pop ( ) ;
3250 if let Some ( connection) = reused {
3351 return Ok ( connection) ;
3452 }
35- AsyncMetaConnection :: connect ( self . addrs . as_slice ( ) ) . await
53+ timed ( timeouts . connect , AsyncMetaConnection :: connect ( self . addrs . as_slice ( ) ) ) . await
3654 }
3755
3856 fn put_back ( & self , connection : AsyncMetaConnection , max_idle : usize ) {
@@ -61,6 +79,7 @@ pub struct AsyncMetaClient {
6179 servers : Arc < Vec < AsyncServer > > ,
6280 hash_function : fn ( & [ u8 ] ) -> u64 ,
6381 max_idle : usize ,
82+ timeouts : Timeouts ,
6483}
6584
6685impl AsyncMetaClient {
@@ -69,8 +88,8 @@ impl AsyncMetaClient {
6988 }
7089
7190 /// Connect to several servers; keys are distributed across them by the
72- /// hash function. One connection per server is dialed up front so
73- /// connection errors surface here .
91+ /// hash function. Addresses are resolved here, but connections are
92+ /// dialed lazily, so a down server surfaces at the first operation .
7493 pub async fn connect_multiple < A : ToSocketAddrs > (
7594 addrs : impl IntoIterator < Item = A > ,
7695 ) -> Result < AsyncMetaClient , MemcacheError > {
@@ -80,13 +99,10 @@ impl AsyncMetaClient {
8099 if resolved. is_empty ( ) {
81100 return Err ( ClientError :: Error ( Cow :: Borrowed ( "address resolved to no socket addresses" ) ) . into ( ) ) ;
82101 }
83- let server = AsyncServer {
102+ servers . push ( AsyncServer {
84103 addrs : resolved,
85104 idle : Mutex :: new ( Vec :: new ( ) ) ,
86- } ;
87- let connection = AsyncMetaConnection :: connect ( server. addrs . as_slice ( ) ) . await ?;
88- server. idle . lock ( ) . unwrap ( ) . push ( connection) ;
89- servers. push ( server) ;
105+ } ) ;
90106 }
91107 if servers. is_empty ( ) {
92108 return Err ( ClientError :: Error ( Cow :: Borrowed ( "at least one server address is required" ) ) . into ( ) ) ;
@@ -95,6 +111,7 @@ impl AsyncMetaClient {
95111 servers : Arc :: new ( servers) ,
96112 hash_function : default_hash_function,
97113 max_idle : DEFAULT_MAX_IDLE ,
114+ timeouts : Timeouts :: default ( ) ,
98115 } )
99116 }
100117
@@ -116,6 +133,23 @@ impl AsyncMetaClient {
116133 self
117134 }
118135
136+ /// Limit how long dialing a server may take (no limit by default).
137+ /// Configure before cloning: clones share connections but not this
138+ /// setting.
139+ pub fn with_connect_timeout ( mut self , timeout : Duration ) -> AsyncMetaClient {
140+ self . timeouts . connect = Some ( timeout) ;
141+ self
142+ }
143+
144+ /// Limit how long one command or batch exchange may take (no limit by
145+ /// default). A timeout poisons the connection like any other transport
146+ /// error. Configure before cloning: clones share connections but not
147+ /// this setting.
148+ pub fn with_io_timeout ( mut self , timeout : Duration ) -> AsyncMetaClient {
149+ self . timeouts . io = Some ( timeout) ;
150+ self
151+ }
152+
119153 fn connection_index ( & self , key : & [ u8 ] ) -> usize {
120154 jump_hash ( ( self . hash_function ) ( key) , self . servers . len ( ) )
121155 }
@@ -155,10 +189,10 @@ impl AsyncMetaClient {
155189 pub async fn run < O : Operation > ( & self , operation : O ) -> Result < O :: Output , MemcacheError > {
156190 let command = operation. prepare ( ) ?;
157191 let server = & self . servers [ self . connection_index ( operation. key ( ) ) ] ;
158- let mut connection = server. checkout ( ) . await ?;
192+ let mut connection = server. checkout ( & self . timeouts ) . await ?;
159193 // A failed exchange leaves the stream in an unknown state, so the
160194 // connection is dropped instead of returned to the pool.
161- let response = connection. execute ( & command) . await ?;
195+ let response = timed ( self . timeouts . io , connection. execute ( & command) ) . await ?;
162196 server. put_back ( connection, self . max_idle ) ;
163197 operation. parse ( parse_meta_result ( response) ?)
164198 }
@@ -187,8 +221,8 @@ impl AsyncMetaClient {
187221 . map ( |& index| plan. commands [ index] . take ( ) . unwrap ( ) )
188222 . collect ( ) ;
189223 let server = & self . servers [ server] ;
190- let mut connection = server. checkout ( ) . await ?;
191- let responses = connection. execute_batch ( & commands) . await ?;
224+ let mut connection = server. checkout ( & self . timeouts ) . await ?;
225+ let responses = timed ( self . timeouts . io , connection. execute_batch ( & commands) ) . await ?;
192226 server. put_back ( connection, self . max_idle ) ;
193227 for ( & index, response) in indices. iter ( ) . zip ( responses) {
194228 outputs[ index] = Some ( operations[ index] . parse ( parse_meta_result ( response) ?) ?) ;
@@ -204,8 +238,8 @@ impl AsyncMetaClient {
204238 /// health check.
205239 pub async fn noop ( & self ) -> Result < ( ) , MemcacheError > {
206240 for server in self . servers . iter ( ) {
207- let mut connection = server. checkout ( ) . await ?;
208- let response = connection. execute ( & build_noop ( ) ) . await ?;
241+ let mut connection = server. checkout ( & self . timeouts ) . await ?;
242+ let response = timed ( self . timeouts . io , connection. execute ( & build_noop ( ) ) ) . await ?;
209243 server. put_back ( connection, self . max_idle ) ;
210244 if response. rc != ReturnCode :: Mn {
211245 return Err ( ServerError :: BadResponse ( "unexpected no-op response" . into ( ) ) . into ( ) ) ;
@@ -219,8 +253,8 @@ impl AsyncMetaClient {
219253 let key = key. into ( ) ;
220254 let server = & self . servers [ self . connection_index ( & key) ] ;
221255 let command = build_debug ( key) ?;
222- let mut connection = server. checkout ( ) . await ?;
223- let response = connection. execute ( & command) . await ?;
256+ let mut connection = server. checkout ( & self . timeouts ) . await ?;
257+ let response = timed ( self . timeouts . io , connection. execute ( & command) ) . await ?;
224258 server. put_back ( connection, self . max_idle ) ;
225259 parse_debug_result ( & response)
226260 }
@@ -233,3 +267,39 @@ impl<'a, O: Operation> Request<'a, AsyncMetaClient, O> {
233267 client. run ( operation) . await
234268 }
235269}
270+
271+ #[ cfg( test) ]
272+ mod tests {
273+ use std:: io:: { BufRead , BufReader , Write } ;
274+ use std:: net:: TcpListener ;
275+
276+ use super :: * ;
277+
278+ #[ tokio:: test]
279+ async fn io_timeout_poisons_connection ( ) {
280+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
281+ let addr = listener. local_addr ( ) . unwrap ( ) ;
282+ let handle = std:: thread:: spawn ( move || {
283+ // First connection: read the request but never respond, so the
284+ // exchange times out and the connection is poisoned.
285+ let ( stream, _) = listener. accept ( ) . unwrap ( ) ;
286+ let mut reader = BufReader :: new ( stream) ;
287+ let mut line = Vec :: new ( ) ;
288+ reader. read_until ( b'\n' , & mut line) . unwrap ( ) ;
289+ // Second connection: respond normally.
290+ let ( stream, _) = listener. accept ( ) . unwrap ( ) ;
291+ let mut reader = BufReader :: new ( stream) ;
292+ let mut line = Vec :: new ( ) ;
293+ reader. read_until ( b'\n' , & mut line) . unwrap ( ) ;
294+ reader. get_mut ( ) . write_all ( b"HD\r \n " ) . unwrap ( ) ;
295+ } ) ;
296+
297+ let client = AsyncMetaClient :: connect ( addr)
298+ . await
299+ . unwrap ( )
300+ . with_io_timeout ( Duration :: from_millis ( 100 ) ) ;
301+ assert ! ( client. delete( "foo" ) . send( ) . await . is_err( ) ) ;
302+ assert ! ( client. delete( "foo" ) . send( ) . await . unwrap( ) . stored( ) ) ;
303+ handle. join ( ) . unwrap ( ) ;
304+ }
305+ }
0 commit comments