@@ -74,6 +74,9 @@ pub struct AnyTlsClientSession {
7474 /// Once taken (by first open_stream), this is None and subsequent streams
7575 /// are sent normally through the channel.
7676 initial_buffer : std:: sync:: Mutex < Option < BytesMut > > ,
77+
78+ /// Notify to break reader/writer loops when session is dropped
79+ close_notify : Arc < tokio:: sync:: Notify > ,
7780}
7881
7982impl std:: fmt:: Debug for AnyTlsClientSession {
@@ -85,6 +88,12 @@ impl std::fmt::Debug for AnyTlsClientSession {
8588 }
8689}
8790
91+ impl Drop for AnyTlsClientSession {
92+ fn drop ( & mut self ) {
93+ self . close_notify . notify_waiters ( ) ;
94+ }
95+ }
96+
8897impl AnyTlsClientSession {
8998 /// Create a new client session on the given transport.
9099 ///
@@ -128,6 +137,7 @@ impl AnyTlsClientSession {
128137 send_padding : AtomicBool :: new ( true ) ,
129138 pkt_counter : AtomicU32 :: new ( 0 ) , // Start at 0, incremented before use
130139 initial_buffer : std:: sync:: Mutex :: new ( Some ( initial_buffer) ) ,
140+ close_notify : Arc :: new ( tokio:: sync:: Notify :: new ( ) ) ,
131141 } ) ;
132142
133143 // NOTE: Settings is NOT sent here - it's in initial_buffer and will be
@@ -209,17 +219,21 @@ impl AnyTlsClientSession {
209219 W : tokio:: io:: AsyncWrite + Send + Unpin + ' static ,
210220 {
211221 // Writer task - handles all outgoing messages (control and data)
212- let session_writer = Arc :: clone ( & session) ;
222+ let session_weak_w = Arc :: downgrade ( & session) ;
223+ let close_notify_w = Arc :: clone ( & session. close_notify ) ;
213224 tokio:: spawn ( async move {
214- if let Err ( e) = Self :: writer_loop ( session_writer, writer, outgoing_rx) . await {
225+ if let Err ( e) =
226+ Self :: writer_loop ( session_weak_w, writer, outgoing_rx, close_notify_w) . await
227+ {
215228 log:: debug!( "AnyTLS client writer ended: {}" , e) ;
216229 }
217230 } ) ;
218231
219232 // Reader task
220- let session_reader = Arc :: clone ( & session) ;
233+ let session_weak_r = Arc :: downgrade ( & session) ;
234+ let close_notify_r = Arc :: clone ( & session. close_notify ) ;
221235 tokio:: spawn ( async move {
222- if let Err ( e) = Self :: reader_loop ( session_reader , reader) . await {
236+ if let Err ( e) = Self :: reader_loop ( session_weak_r , reader, close_notify_r ) . await {
223237 log:: debug!( "AnyTLS client reader ended: {}" , e) ;
224238 }
225239 } ) ;
@@ -236,9 +250,10 @@ impl AnyTlsClientSession {
236250 /// - Padding frames concatenated with payload before write (single syscall)
237251 /// - Zero-allocation padding using put_bytes()
238252 async fn writer_loop < W > (
239- session : Arc < Self > ,
253+ session_weak : std :: sync :: Weak < Self > ,
240254 mut writer : W ,
241255 mut outgoing_rx : mpsc:: UnboundedReceiver < OutgoingMessage > ,
256+ close_notify : Arc < tokio:: sync:: Notify > ,
242257 ) -> io:: Result < ( ) >
243258 where
244259 W : tokio:: io:: AsyncWrite + Send + Unpin ,
@@ -253,7 +268,28 @@ impl AnyTlsClientSession {
253268 // Used to ensure single write() call per padding segment
254269 let mut padding_buf = BytesMut :: with_capacity ( 65536 + FRAME_HEADER_SIZE * 2 + 64 ) ;
255270
256- while let Some ( msg) = outgoing_rx. recv ( ) . await {
271+ loop {
272+ let msg = tokio:: select! {
273+ m = outgoing_rx. recv( ) => m,
274+ _ = close_notify. notified( ) => {
275+ log:: debug!( "AnyTLS client writer loop: close_notify triggered" ) ;
276+ break ;
277+ }
278+ } ;
279+
280+ let msg = match msg {
281+ Some ( m) => m,
282+ None => break ,
283+ } ;
284+
285+ let session = match session_weak. upgrade ( ) {
286+ Some ( s) => s,
287+ None => {
288+ log:: debug!( "AnyTLS client writer loop: session dropped, exiting" ) ;
289+ break ;
290+ }
291+ } ;
292+
257293 if session. is_closed . load ( Ordering :: Relaxed ) {
258294 break ;
259295 }
@@ -422,37 +458,74 @@ impl AnyTlsClientSession {
422458 }
423459
424460 /// Reader loop - receives frames from the transport
425- async fn reader_loop < R > ( session : Arc < Self > , mut reader : R ) -> io:: Result < ( ) >
461+ async fn reader_loop < R > (
462+ session_weak : std:: sync:: Weak < Self > ,
463+ mut reader : R ,
464+ close_notify : Arc < tokio:: sync:: Notify > ,
465+ ) -> io:: Result < ( ) >
426466 where
427467 R : tokio:: io:: AsyncRead + Send + Unpin ,
428468 {
429469 log:: debug!( "AnyTLS client reader loop started" ) ;
430470 let mut buffer = BytesMut :: with_capacity ( 8192 ) ;
431471
432472 loop {
433- if session. is_closed . load ( Ordering :: Relaxed ) {
434- log:: debug!( "AnyTLS client reader loop: session closed, exiting" ) ;
473+ // Scope for the strong reference to session
474+ let has_closed = {
475+ let session = match session_weak. upgrade ( ) {
476+ Some ( s) => s,
477+ None => {
478+ log:: debug!( "AnyTLS client reader loop: session dropped, exiting" ) ;
479+ return Ok ( ( ) ) ;
480+ }
481+ } ;
482+
483+ if session. is_closed . load ( Ordering :: Relaxed ) {
484+ log:: debug!( "AnyTLS client reader loop: session closed, exiting" ) ;
485+ return Ok ( ( ) ) ;
486+ }
487+
488+ // Decode any frames already in buffer
489+ while let Some ( frame) = FrameCodec :: decode ( & mut buffer) ? {
490+ log:: debug!(
491+ "AnyTLS client received frame: {:?} stream={} len={}" ,
492+ frame. cmd,
493+ frame. stream_id,
494+ frame. data. len( )
495+ ) ;
496+ if let Err ( e) = session. handle_frame ( frame) . await {
497+ log:: warn!( "AnyTLS client error handling frame: {}" , e) ;
498+ return Err ( e) ;
499+ }
500+ }
501+
502+ false
503+ } ;
504+
505+ if has_closed {
435506 return Ok ( ( ) ) ;
436507 }
437508
438- // Decode any frames already in buffer
439- while let Some ( frame) = FrameCodec :: decode ( & mut buffer) ? {
440- log:: debug!(
441- "AnyTLS client received frame: {:?} stream={} len={}" ,
442- frame. cmd,
443- frame. stream_id,
444- frame. data. len( )
445- ) ;
446- if let Err ( e) = session. handle_frame ( frame) . await {
447- log:: warn!( "AnyTLS client error handling frame: {}" , e) ;
448- return Err ( e) ;
509+ // DO NOT hold `session` (strong Arc) across `reader.read_buf().await`.
510+ // Because if we hold the Arc, the Drop impl will never be called when the stream disconnects!
511+ // Wait for new data or close_notify
512+ let read_result = tokio:: select! {
513+ res = reader. read_buf( & mut buffer) => res,
514+ _ = close_notify. notified( ) => {
515+ log:: debug!( "AnyTLS client reader loop: close_notify triggered" ) ;
516+ return Ok ( ( ) ) ;
449517 }
450- }
518+ } ;
451519
452- // Read more data
453- let n = reader. read_buf ( & mut buffer) . await ?;
520+ let n = read_result?;
454521 if n == 0 {
455522 log:: debug!( "AnyTLS client reader loop: connection closed (EOF)" ) ;
523+ // Once reader gets EOF from upstream, TLS connection is dead.
524+ // Re-upgrade to signal writer loop
525+ if let Some ( session) = session_weak. upgrade ( ) {
526+ session. is_closed . store ( true , Ordering :: Relaxed ) ;
527+ session. close_notify . notify_waiters ( ) ;
528+ }
456529 return Ok ( ( ) ) ; // Connection closed
457530 }
458531 log:: debug!( "AnyTLS client reader: read {} bytes" , n) ;
0 commit comments