perf: bound idle TCP reuse storage - #6
Conversation
|
Codex (@codex) review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8dd761f02d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
| if n.freeListenerPool.slots == nil && len(listener.pool.slots) <= maxIdleTCPListenerSlots && len(listener.pool.storage) <= maxIdleTCPReuseStorageBytes { | ||
| n.freeListenerPool = listener.pool | ||
| } else { | ||
| listener.pool.destroyLocked() |
There was a problem hiding this comment.
Avoid clearing rejected listener pools twice
When a listener pool cannot be cached because another idle pool exists or it exceeds the slot/storage ceiling, recycleListenerLocked has already called releaseLocked, whose new clear(p.storage) wipes the entire backing allocation. Calling destroyLocked here invokes releaseLocked again, so every rejected pool is cleared twice; with valid listener pools reaching 256 MiB and bursts rejecting all but one pool, close can synchronously write hundreds of extra MiB. Drop the already-released pool without invoking the clearing path again.
Useful? React with 👍 / 👎.
Summary
Bound lneto TCP's idle listener/outbound reuse state so closing quota-owned resources cannot leave a cache proportional to the configured high-water concurrency.
MaxListeners- andMaxOutboundStreams-sized idle-cache index arrays with one listener-pool slot and one outbound-buffer slottcp.Conngeneration-distinct because the stack may retain its registration pointer after abort; only host-owned bytes/accounting are reusedWhy
The previous adapter preallocated idle-cache index arrays from the configured maximum counts and appended every closed listener pool and outbound stream buffer. The memory was finite, but after quota release the adapter could continue retaining the full concurrency high-water mark. Listener backing bytes were also reset structurally without explicit zeroing.
The new cache is independent of configured concurrency and has explicit byte/metadata ceilings. Listener and stream registries also grow with actual use from small initial hints. Common reopen/reconnect loops still reuse one cleared allocation and quota-accounting object, while bursts and unusually large configurations return memory to the runtime after deterministic cleanup. The lneto connection object itself remains stream-owned and is never recycled into a new wrapper.
Measurements
Controlled linux/amd64 Go 1.24.4 focused benchmarks retain the existing steady listen/connect allocation counts. Ordinary adapter construction improves from:
At the valid maximum-listener configuration, adapter construction improves from:
Steady outbound connect/close allocation also improves from 1,128 to 936 B/op (17.0%) with the same four allocations, while the focused median improves by roughly 3%. This comes from detaching the reusable quota charge from each generation-distinct stream wrapper.
Together these changes remove eager reuse-index allocations and the listener registry allocation proportional to the configured maximum. Close-path timing is treated as informational because listener storage now receives an intentional bounded clear before reuse/release.
Validation
Passed locally and in hosted run
30202877938on head8dd761f:go test -shuffle=on -count=1 ./...go vet ./...scripts/check-source-boundaries.shinternal/backend/lneto/tcpand publictcpScope
This is stacked directly on draft PR #3 (
agent/tls-full-grid) and is independent of the DNS fallback and TLS allocation-hardening siblings. It changes no capability, import, policy, quota, port, or guest ABI contract. Keep this PR as a draft while its base is unmerged.