Repository navigation
atunnel uses less memory for idle egress connections - #2908
Merged
Bowei Du (bowei) merged 4 commits intoOct 21, 2026
Merged
Conversation
Each egress tunnel held about 157 KiB while open, even with no data moving: a 32 KiB io.Copy buffer per direction, plus a 32 KiB buffer held by the HTTP/2 stream's reader goroutine. With a 1 GiB limit, about 6,000 idle connections were enough to OOM-kill atunnel. copyBothWays now waits for data with a 512 B buffer and borrows a pooled 32 KiB buffer only while data is moving. h2StreamConn.WriteTo reads the response body directly, so the reader goroutine and its buffer are gone. With a read deadline set, it falls back to Read.
Chuang Wang (chuangw6)
requested review from
Lior Lieberman (LiorLieberman) and
Bowei Du (bowei)
October 19, 2026 21:05
Collaborator
|
Can we see how the numbers change if we have a 512 byte, 1k, 2k byte buffers? I'm curious whether or not this makes an impact. |
Collaborator
Author
|
Bowei Du (@bowei) Ran 512 B, 1 KiB, 2 KiB and 4 KiB (same setup, 1 CPU / 1 GiB):
Idle memory grows by about 2 KiB per extra KiB (one buffer per |
Lior Lieberman (LiorLieberman)
previously approved these changes
Oct 20, 2026
Lior Lieberman (LiorLieberman)
left a comment
Collaborator
There was a problem hiding this comment.
LGTM. Thanks!
Bowei Du (bowei)
previously approved these changes
Oct 20, 2026
Bowei Du (bowei)
approved these changes
Oct 21, 2026
2 tasks done
igooch
pushed a commit
to igooch/substrate
that referenced
this pull request
Oct 21, 2026
…strate#3072) EgressIdleConns couldn't get past 64,512 connections. All of them came from the egress actor in one sandbox, and atunnel's redirect sends every connection to the same listener, so only the source port tells them apart. That's a per-actor limit, not a per-worker one. The worker now runs an egress actor in every sandbox, and EgressIdleConns splits its connections across them. The other egress scenarios still use actor-0's, so their numbers stay comparable with earlier runs. Also adds a 100k run, with atunnel at 4 CPUs / 16 GiB so neither limit is reached: | idle connections | per connection (after GC) | goroutines/conn | atunnel cgroup memory | CPU opening 200 conns/s | |---|---|---|---|---| | 25,000 | 26.9 KiB | 4.0 | 1.3 GiB | 0.15 cores | | 50,000 | 27.1 KiB | 4.0 | 2.6 GiB | 0.14 cores | | 100,000 | 26.8 KiB | 4.0 | 5.2 GiB | 0.19 cores | Memory per connection stays flat. Before agent-substrate#2908 it was about 157 KiB. The CPU is what opening new connections costs (about 1 ms each); holding the idle ones costs almost none. - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR
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.
Fixes #2759
Each idle egress tunnel held about 157 KiB and 5 goroutines: a 32 KiB
io.Copy buffer per direction in copyBothWays, and a 32 KiB buffer held
by the HTTP/2 stream's reader goroutine while it waited. At a 1 GiB
limit, atunnel was OOM-killed at around 6,000 idle connections, which
breaks egress for every actor on the worker.
Changes:
32 KiB buffer only while data is moving.
doesn't start a reader goroutine per stream. With a read deadline
set, it falls back to Read.
benchmarking/atunnel, 1 CPU, 1 GiB:
EgressTCP is about 3% slower because bulk copies do a few extra small
reads. Most of the remaining 29 KiB is net/http's 16 KiB HTTP/2 send
buffer, which this doesn't touch.