Conversation
…owth (#1) The AnyTLS client session's unified writer channel was created with mpsc::unbounded_channel(). When the writer loop exits (close_notify on session drop, or is_closed on EOF/error) while the session is still referenced by live streams, the unbounded sender side stays alive and any message sent in that window queues into the channel forever — no receiver, no backpressure. This caused RAM to slowly climb (80MB -> 100MB+) and eventually OOM. Switch to a bounded channel so a dead writer applies backpressure instead of silently accumulating. Also break the per-stream forwarding task on close_notify so it never blocks on a gone writer. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
… memory leak on dropped/errored connections (#3) Reality/VLESS fallback paths spawned copy_bidirectional tasks with no timeout, no idle cap, and no lifetime link to the parent connection. A half-open (dropped/errored) client therefore kept the task, both sockets, and copy buffers alive until TCP keepalive fired (~7 min), so memory grew without bound under connection churn. Wrap the fallback copy in a 300s timeout so every fallback task is guaranteed to terminate. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
AYastrebov
added a commit
to AYastrebov/shoes
that referenced
this pull request
Aug 9, 2026
…dget The AnyTLS client session's unified writer channel was created with mpsc::unbounded_channel(). When the writer loop exits -- close_notify on session drop, or is_closed on EOF/error -- while the session is still referenced by live streams, the unbounded sender side stays alive and any message sent in that window queues into the channel forever: no receiver, no backpressure. RAM climbed slowly, 80 MB to 100 MB and beyond, and eventually OOMed. Switch to a bounded channel so a dead writer applies backpressure instead of silently accumulating, and break the per-stream forwarding task on close_notify so it never blocks on a writer that is gone. The upstream fix (cfal#153) is right in shape but its capacity was STREAM_CHANNEL_BUFFER * 1024, i.e. 16,384 messages. Each queued message owns a Bytes that can hold a full frame, so that bound is a few hundred MB. An iOS packet-tunnel extension has about 50 MB in total, so it would be killed long before the backpressure ever engaged. 256 keeps the ceiling at a few MB while staying deep enough that the writer loop is not a bottleneck. Named and documented rather than derived from the per-stream constant, since the two bound different things. Only the AnyTLS client commit from that PR is taken. Its second commit bounds the Reality/VLESS *server* fallback paths, which this fork does not run. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AYastrebov
added a commit
to AYastrebov/shoes
that referenced
this pull request
Aug 24, 2026
…dget The AnyTLS client session's unified writer channel was created with mpsc::unbounded_channel(). When the writer loop exits -- close_notify on session drop, or is_closed on EOF/error -- while the session is still referenced by live streams, the unbounded sender side stays alive and any message sent in that window queues into the channel forever: no receiver, no backpressure. RAM climbed slowly, 80 MB to 100 MB and beyond, and eventually OOMed. Switch to a bounded channel so a dead writer applies backpressure instead of silently accumulating, and break the per-stream forwarding task on close_notify so it never blocks on a writer that is gone. The upstream fix (cfal#153) is right in shape but its capacity was STREAM_CHANNEL_BUFFER * 1024, i.e. 16,384 messages. Each queued message owns a Bytes that can hold a full frame, so that bound is a few hundred MB. An iOS packet-tunnel extension has about 50 MB in total, so it would be killed long before the backpressure ever engaged. 256 keeps the ceiling at a few MB while staying deep enough that the writer loop is not a bottleneck. Named and documented rather than derived from the per-stream constant, since the two bound different things. Only the AnyTLS client commit from that PR is taken. Its second commit bounds the Reality/VLESS *server* fallback paths, which this fork does not run. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AYastrebov
added a commit
to AYastrebov/shoes
that referenced
this pull request
Aug 24, 2026
…dget The AnyTLS client session's unified writer channel was created with mpsc::unbounded_channel(). When the writer loop exits -- close_notify on session drop, or is_closed on EOF/error -- while the session is still referenced by live streams, the unbounded sender side stays alive and any message sent in that window queues into the channel forever: no receiver, no backpressure. RAM climbed slowly, 80 MB to 100 MB and beyond, and eventually OOMed. Switch to a bounded channel so a dead writer applies backpressure instead of silently accumulating, and break the per-stream forwarding task on close_notify so it never blocks on a writer that is gone. The upstream fix (cfal#153) is right in shape but its capacity was STREAM_CHANNEL_BUFFER * 1024, i.e. 16,384 messages. Each queued message owns a Bytes that can hold a full frame, so that bound is a few hundred MB. An iOS packet-tunnel extension has about 50 MB in total, so it would be killed long before the backpressure ever engaged. 256 keeps the ceiling at a few MB while staying deep enough that the writer loop is not a bottleneck. Named and documented rather than derived from the per-stream constant, since the two bound different things. Only the AnyTLS client commit from that PR is taken. Its second commit bounds the Reality/VLESS *server* fallback paths, which this fork does not run. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The AnyTLS client session’s unified writer channel was created with
mpsc::unbounded_channel(). When the writer loop exited (close_notify on session drop, or is_closed on EOF/error) while the session was still referenced by live streams, the unbounded sender side stayed alive. Any messages sent in that window queued into the channel forever - no receiver, no backpressure. This caused RAM to slowly climb (80MB → 100MB+) and eventually OOM.Switched to a bounded channel so a dead writer applies backpressure instead of silently accumulating messages. Also made the per-stream forwarding task break on
close_notifyso it never blocks on a gone writer.