Fix WAN speed test upload accuracy and GC crash#276
Merged
tvancott42 merged 9 commits intomainfrom Feb 15, 2026
Merged
Conversation
Without reading the response body to EOF before closing, Go's HTTP/1.1 client cannot reuse the connection. Every upload POST was creating a new TCP + TLS handshake (~8ms overhead per 5MB chunk on 4ms RTT).
Testing showed 8 streams saturates GPON upstream better than 6 (avg 1012 vs 946 Mbps upload). Drain upload response body in .NET to enable TCP connection reuse. Update UI text to say "multiple" instead of hardcoding the stream count.
Aggressive GC with compacting corrupts pinned pipe handles from the iperf3 process stream reader, causing AccessViolationException crashes. Use Optimized + non-blocking instead to hint the runtime without forcing memory compaction over active async handles.
Compacting GC moves live objects to new addresses, corrupting in-flight Spans and pipe handles held by background threads (iperf3 stream reader). Forced + blocking + no compaction sweeps dead objects without relocating live ones, safely reclaiming memory while preserving pointer validity.
CompactOnce targets only the Large Object Heap (upload payloads, HTTP buffers >85KB) without touching the Small Object Heap where pipe handles and async state machines live. This reclaims the ~1.2 GB upload memory while avoiding the AccessViolationException from full-heap compaction.
Manual GC.Collect with compacting caused AccessViolationException by corrupting in-flight Spans and pipe handles. Rather than finding the right GC incantation, remove all manual GC calls and let the runtime handle memory pressure naturally. Testing with DOTNET_GCHeapHardLimit on Mac to validate behavior under memory constraints.
GC compaction while iperf3 pipe readers are active causes AccessViolationException. Pause the iperf3 process before the Cloudflare speed test and resume it after.
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.
Summary
Increase concurrent streams from 6 to 8 - Testing showed 8 streams consistently produces better upload throughput (avg 1075 Mbps vs 946 Mbps with 6 streams), while download stays the same (~940 Mbps). Applied to both the Go gateway binary and .NET service.
Drain upload response body for TCP connection reuse - HTTP/1.1 requires the response body to be fully read before the connection can be reused. Without this, each upload request opens a new TCP connection instead of reusing the existing one. Fixed in both Go (
io.Copy(io.Discard, resp.Body)) and .NET (CopyToAsync(Stream.Null)).Pause iperf3 server during WAN speed tests - The speed test's memory pressure can trigger GC compaction, which corrupts active pipe handles from the iperf3 child process, causing
AccessViolationExceptioncrashes. The iperf3 server is now paused before the test and resumed after, eliminating the pipe handles during the memory-intensive phase.Remove manual GC calls - The aggressive
GC.Collectcalls between download/upload phases were the direct cause of the crash. With iperf3 paused, the runtime's own GC can safely compact without corrupting pipe handles.Test plan