@@ -16,7 +16,7 @@ use backoff::ExponentialBackoff;
1616use rand:: RngCore ;
1717use std:: collections:: HashMap ;
1818use std:: path:: Path ;
19- use std:: sync:: Arc ;
19+ use std:: sync:: { Arc , Weak } ;
2020use std:: time:: Duration ;
2121use tokio:: io:: { self , copy_bidirectional, AsyncReadExt , AsyncWriteExt } ;
2222use tokio:: net:: { TcpListener , TcpStream , UdpSocket } ;
@@ -355,8 +355,13 @@ async fn do_control_channel_handshake<T: 'static + Transport>(
355355 conn. flush ( ) . await ?;
356356
357357 info ! ( service = %service_config. name, "Control channel established" ) ;
358- let handle =
359- ControlChannelHandle :: new ( conn, service_config, server_config. heartbeat_interval ) ;
358+ let handle = ControlChannelHandle :: new (
359+ conn,
360+ service_config,
361+ server_config. heartbeat_interval ,
362+ Arc :: downgrade ( & control_channels) ,
363+ session_key,
364+ ) ;
360365
361366 // Insert the new handle
362367 let _ = h. insert ( service_digest, session_key, handle) ;
@@ -410,6 +415,8 @@ where
410415 conn : T :: Stream ,
411416 service : ServerServiceConfig ,
412417 heartbeat_interval : u64 ,
418+ control_channels : Weak < RwLock < ControlChannelMap < T > > > ,
419+ session_key : Nonce ,
413420 ) -> ControlChannelHandle < T > {
414421 // Create a shutdown channel
415422 let ( shutdown_tx, shutdown_rx) = broadcast:: channel :: < bool > ( 1 ) ;
@@ -505,12 +512,17 @@ where
505512 heartbeat_interval,
506513 } ;
507514
508- // Run the control channel
515+ // On exit, drop the handle so `shutdown_tx` drops and the data channel pool
516+ // closes; otherwise its sockets leak. session_key is unique, so this never
517+ // removes a reconnected session.
509518 tokio:: spawn (
510519 async move {
511520 if let Err ( err) = ch. run ( ) . await {
512521 error ! ( "{:#}" , err) ;
513522 }
523+ if let Some ( control_channels) = control_channels. upgrade ( ) {
524+ control_channels. write ( ) . await . remove2 ( & session_key) ;
525+ }
514526 }
515527 . instrument ( Span :: current ( ) ) ,
516528 ) ;
@@ -532,25 +544,48 @@ struct ControlChannel<T: Transport> {
532544}
533545
534546impl < T : Transport > ControlChannel < T > {
535- async fn write_and_flush ( & mut self , data : & [ u8 ] ) -> Result < ( ) > {
536- write_and_flush ( & mut self . conn , data)
537- . await
538- . with_context ( || "Failed to write control cmds" ) ?;
539- Ok ( ( ) )
540- }
541547 // Run a control channel
542548 #[ instrument( skip_all) ]
543- async fn run ( mut self ) -> Result < ( ) > {
549+ async fn run ( self ) -> Result < ( ) > {
544550 let create_ch_cmd = bincode:: serialize ( & ControlChannelCmd :: CreateDataChannel ) . unwrap ( ) ;
545551 let heartbeat = bincode:: serialize ( & ControlChannelCmd :: HeartBeat ) . unwrap ( ) ;
546552
553+ // Split so we can read (to detect a dead client) and write concurrently.
554+ let ControlChannel {
555+ conn,
556+ mut shutdown_rx,
557+ mut data_ch_req_rx,
558+ heartbeat_interval,
559+ } = self ;
560+ let ( mut rd, mut wr) = tokio:: io:: split ( conn) ;
561+ let mut probe = [ 0u8 ; 1 ] ;
562+
547563 // Wait for data channel requests and the shutdown signal
548564 loop {
549565 tokio:: select! {
550- val = self . data_ch_req_rx. recv( ) => {
566+ // The client sends nothing after the handshake, so any completed
567+ // read means it's gone. Heartbeat writes alone never notice a
568+ // half-closed client, which leaks its sockets.
569+ res = rd. read( & mut probe) => {
570+ match res {
571+ Ok ( 0 ) => {
572+ debug!( "Control channel closed by the client" ) ;
573+ break ;
574+ }
575+ Ok ( bytes_read) => {
576+ warn!( bytes_read, "Unexpected data on control channel" ) ;
577+ break ;
578+ }
579+ Err ( e) => {
580+ error!( "Control channel read error: {:#}" , e) ;
581+ break ;
582+ }
583+ }
584+ } ,
585+ val = data_ch_req_rx. recv( ) => {
551586 match val {
552587 Some ( _) => {
553- if let Err ( e) = self . write_and_flush( & create_ch_cmd) . await {
588+ if let Err ( e) = write_and_flush( & mut wr , & create_ch_cmd) . await {
554589 error!( "{:#}" , e) ;
555590 break ;
556591 }
@@ -560,14 +595,14 @@ impl<T: Transport> ControlChannel<T> {
560595 }
561596 }
562597 } ,
563- _ = time:: sleep( Duration :: from_secs( self . heartbeat_interval) ) , if self . heartbeat_interval != 0 => {
564- if let Err ( e) = self . write_and_flush( & heartbeat) . await {
598+ _ = time:: sleep( Duration :: from_secs( heartbeat_interval) ) , if heartbeat_interval != 0 => {
599+ if let Err ( e) = write_and_flush( & mut wr , & heartbeat) . await {
565600 error!( "{:#}" , e) ;
566601 break ;
567602 }
568603 }
569604 // Wait for the shutdown signal
570- _ = self . shutdown_rx. recv( ) => {
605+ _ = shutdown_rx. recv( ) => {
571606 break ;
572607 }
573608 }
@@ -888,3 +923,159 @@ async fn run_udp_connection_pool<T: Transport>(
888923
889924 Ok ( ( ) )
890925}
926+
927+ #[ cfg( test) ]
928+ mod tests {
929+ use super :: * ;
930+ use std:: io:: ErrorKind ;
931+ use std:: net:: SocketAddr ;
932+
933+ const CLEANUP_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
934+
935+ fn unused_tcp_addr ( ) -> Result < SocketAddr > {
936+ let listener = std:: net:: TcpListener :: bind ( "127.0.0.1:0" ) ?;
937+ Ok ( listener. local_addr ( ) ?)
938+ }
939+
940+ async fn connected_tcp_pair ( ) -> Result < ( TcpStream , TcpStream ) > {
941+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . await ?;
942+ let addr = listener. local_addr ( ) ?;
943+ let ( client, ( server, _) ) = tokio:: try_join!( TcpStream :: connect( addr) , listener. accept( ) ) ?;
944+ Ok ( ( server, client) )
945+ }
946+
947+ async fn wait_until_listener_is_bound ( addr : SocketAddr ) -> Result < ( ) > {
948+ time:: timeout ( CLEANUP_TIMEOUT , async {
949+ loop {
950+ match TcpListener :: bind ( addr) . await {
951+ Ok ( listener) => {
952+ drop ( listener) ;
953+ time:: sleep ( Duration :: from_millis ( 10 ) ) . await ;
954+ }
955+ Err ( error) if error. kind ( ) == ErrorKind :: AddrInUse => return Ok ( ( ) ) ,
956+ Err ( error) => return Err ( error. into ( ) ) ,
957+ }
958+ }
959+ } )
960+ . await
961+ . context ( "service listener was not created before the deadline" ) ?
962+ }
963+
964+ async fn wait_until_listener_is_released ( addr : SocketAddr ) -> Result < TcpListener > {
965+ time:: timeout ( CLEANUP_TIMEOUT , async {
966+ loop {
967+ match TcpListener :: bind ( addr) . await {
968+ Ok ( listener) => return Ok ( listener) ,
969+ Err ( error) if error. kind ( ) == ErrorKind :: AddrInUse => {
970+ time:: sleep ( Duration :: from_millis ( 10 ) ) . await ;
971+ }
972+ Err ( error) => return Err ( error. into ( ) ) ,
973+ }
974+ }
975+ } )
976+ . await
977+ . context ( "service listener was not released before the deadline" ) ?
978+ }
979+
980+ async fn wait_until_channel_is_removed (
981+ control_channels : & RwLock < ControlChannelMap < TcpTransport > > ,
982+ service_digest : ServiceDigest ,
983+ session_key : Nonce ,
984+ ) -> Result < ( ) > {
985+ time:: timeout ( CLEANUP_TIMEOUT , async {
986+ loop {
987+ let removed = {
988+ let channels = control_channels. read ( ) . await ;
989+ channels. get1 ( & service_digest) . is_none ( )
990+ && channels. get2 ( & session_key) . is_none ( )
991+ } ;
992+ if removed {
993+ return ;
994+ }
995+ time:: sleep ( Duration :: from_millis ( 10 ) ) . await ;
996+ }
997+ } )
998+ . await
999+ . context ( "control channel was not removed before the deadline" ) ?;
1000+ Ok ( ( ) )
1001+ }
1002+
1003+ fn tcp_service ( bind_addr : SocketAddr ) -> ServerServiceConfig {
1004+ ServerServiceConfig {
1005+ service_type : ServiceType :: Tcp ,
1006+ name : "cleanup-test" . to_owned ( ) ,
1007+ bind_addr : bind_addr. to_string ( ) ,
1008+ ..Default :: default ( )
1009+ }
1010+ }
1011+
1012+ #[ tokio:: test]
1013+ async fn half_closed_control_channel_releases_map_entry_and_listener_without_heartbeat (
1014+ ) -> Result < ( ) > {
1015+ let service_addr = unused_tcp_addr ( ) ?;
1016+ let ( server_conn, mut client_conn) = connected_tcp_pair ( ) . await ?;
1017+ let service_digest = [ 1_u8 ; HASH_WIDTH_IN_BYTES ] ;
1018+ let session_key = [ 2_u8 ; HASH_WIDTH_IN_BYTES ] ;
1019+ let control_channels = Arc :: new ( RwLock :: new ( ControlChannelMap :: new ( ) ) ) ;
1020+
1021+ {
1022+ // Match the handshake's locking order so cleanup cannot run before insertion.
1023+ let mut channels = control_channels. write ( ) . await ;
1024+ let handle = ControlChannelHandle :: < TcpTransport > :: new (
1025+ server_conn,
1026+ tcp_service ( service_addr) ,
1027+ 0 ,
1028+ Arc :: downgrade ( & control_channels) ,
1029+ session_key,
1030+ ) ;
1031+ if channels
1032+ . insert ( service_digest, session_key, handle)
1033+ . is_err ( )
1034+ {
1035+ bail ! ( "failed to insert test control channel" ) ;
1036+ }
1037+ }
1038+
1039+ wait_until_listener_is_bound ( service_addr) . await ?;
1040+ client_conn. shutdown ( ) . await ?;
1041+
1042+ wait_until_channel_is_removed ( & control_channels, service_digest, session_key) . await ?;
1043+ let released_listener = wait_until_listener_is_released ( service_addr) . await ?;
1044+ drop ( released_listener) ;
1045+ drop ( client_conn) ;
1046+ Ok ( ( ) )
1047+ }
1048+
1049+ #[ tokio:: test]
1050+ async fn dropping_channel_map_releases_listener_without_retaining_cycle ( ) -> Result < ( ) > {
1051+ let service_addr = unused_tcp_addr ( ) ?;
1052+ let ( server_conn, _client_conn) = connected_tcp_pair ( ) . await ?;
1053+ let service_digest = [ 3_u8 ; HASH_WIDTH_IN_BYTES ] ;
1054+ let session_key = [ 4_u8 ; HASH_WIDTH_IN_BYTES ] ;
1055+ let control_channels = Arc :: new ( RwLock :: new ( ControlChannelMap :: new ( ) ) ) ;
1056+
1057+ {
1058+ let mut channels = control_channels. write ( ) . await ;
1059+ let handle = ControlChannelHandle :: < TcpTransport > :: new (
1060+ server_conn,
1061+ tcp_service ( service_addr) ,
1062+ 0 ,
1063+ Arc :: downgrade ( & control_channels) ,
1064+ session_key,
1065+ ) ;
1066+ if channels
1067+ . insert ( service_digest, session_key, handle)
1068+ . is_err ( )
1069+ {
1070+ bail ! ( "failed to insert test control channel" ) ;
1071+ }
1072+ }
1073+
1074+ wait_until_listener_is_bound ( service_addr) . await ?;
1075+ drop ( control_channels) ;
1076+
1077+ let released_listener = wait_until_listener_is_released ( service_addr) . await ?;
1078+ drop ( released_listener) ;
1079+ Ok ( ( ) )
1080+ }
1081+ }
0 commit comments