Skip to content

Commit dba023c

Browse files
authored
fix AnyTLS memory leaks (#131)
1 parent 80f9108 commit dba023c

1 file changed

Lines changed: 96 additions & 23 deletions

File tree

src/anytls/anytls_client_session.rs

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7982
impl 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+
8897
impl 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

Comments
 (0)