Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

## `github.com/quic-go/quic-go` `v0.64.1`
## `github.com/quic-go/quic-go` `v0.65.0`

### `LICENSE`

Expand Down Expand Up @@ -529,7 +529,7 @@ rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
```

## `golang.org/x/net` `v0.61.0`
## `golang.org/x/net` `v0.62.0`

### `LICENSE`

Expand Down
7 changes: 5 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,11 @@ Capacity history is a recent maximum of eight eligible delivery samples, not
an average or a promise of a monotonically decreasing target. Higher delivery
evidence can raise it even in a pacing-dominated interval. The shared-stream
regression test therefore witnesses actual peer-received flow-control blocking
and checks learning at a fixed, still-backpressured feedback checkpoint. It
also drains the remaining payload and verifies same-connection recovery;
and checks learning at a fixed, still-backpressured feedback checkpoint. Its
slow readers release credit in small, spaced reads rather than two large
bursts, to reduce short high-rate samples caused by scheduler skew between
large drains. Both aligned and deliberately skewed readers retain the same
checkpoint and capacity-reduction assertion. It also drains the remaining payload and verifies same-connection recovery;
an arbitrary final target alone is not evidence that capacity never adapted.

Web H2/H3 streams do not use the native pacing negotiation and report client
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ replace github.com/apernet/quic-go => github.com/cppla/quic-go v0.63.1-0.2026100
require (
github.com/apernet/quic-go v0.63.1-0.20261004180939-a10df75c260c
github.com/quic-go/qpack v0.6.0
github.com/quic-go/quic-go v0.64.1
github.com/quic-go/quic-go v0.65.0
github.com/refraction-networking/utls v1.8.3-0.20261006222701-ff1b50fbbe9a
golang.org/x/net v0.61.0
golang.org/x/net v0.62.0
)

require (
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.64.1 h1:BCNTP+6x6++OPKrGST2FR3f1tycx8O3QOA3QQv09g70=
github.com/quic-go/quic-go v0.64.1/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w=
github.com/quic-go/quic-go v0.65.0 h1:BJQSjdTvLPLmWtaCupUphRbgxgpZhdO/GMpr9Eoe3B4=
github.com/quic-go/quic-go v0.65.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
Expand All @@ -20,8 +20,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
Expand Down
63 changes: 48 additions & 15 deletions internal/tunnel/pacing_feedback_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ func TestQUICPacingSustainedDownloadDoesNotSelfLimitToFloor(t *testing.T) {
}

func TestQUICPacingSharedStreamsAcceptTransportBackpressure(t *testing.T) {
for _, skew := range []time.Duration{0, 15 * time.Millisecond} {
t.Run(fmt.Sprintf("reader-skew-%s", skew), func(t *testing.T) {
testQUICPacingSharedStreamsAcceptTransportBackpressure(t, skew)
})
}
}

func testQUICPacingSharedStreamsAcceptTransportBackpressure(t *testing.T, readerSkew time.Duration) {
t.Helper()
// Each stream's fixed 16 KiB receive window is smaller than one 32 KiB
// write, and cannot auto-grow to absorb the entire slow-reader payload.
trace := newFeedbackSharedTrace()
Expand Down Expand Up @@ -80,8 +89,12 @@ func TestQUICPacingSharedStreamsAcceptTransportBackpressure(t *testing.T) {
// Start a fast sibling while the primary cannot drain its finite receive
// buffer. The real clock observes actual token sleeps, not merely calls to
// Wait, while the primary is inside the underlying QUIC stream.Write.
siblingBurst := f.run(func() error { return writeFeedbackPayload(f.serverStreams[1], 128<<10) })
if err := readFeedbackPayload(f.clientStreams[1], 128<<10); err != nil {
// A short burst can fit the shared bucket plus tokens refilled during
// transport work. The same fixed 1 MiB warmup as the primary gives this
// phase more real writes in which to witness a token sleep under race.
const siblingWarmupBytes = 1 << 20
siblingBurst := f.run(func() error { return writeFeedbackPayload(f.serverStreams[1], siblingWarmupBytes) })
if err := readFeedbackPayload(f.clientStreams[1], siblingWarmupBytes); err != nil {
t.Fatal(err)
}
f.await(t, siblingBurst)
Expand All @@ -93,32 +106,41 @@ func TestQUICPacingSharedStreamsAcceptTransportBackpressure(t *testing.T) {
// Both streams now have slow readers. Unlike one blocked stream with an
// unrestricted sibling, this reduces delivery for the shared connection.
// Arm this immutable phase only after the preceding fast writer joined.
siblingWitness := newFeedbackSharedWitness(f.rawStreams[1], trace.streams[1], 128<<10, checkpoint, receiptQueue)
siblingWitness := newFeedbackSharedWitness(f.rawStreams[1], trace.streams[1], siblingWarmupBytes, checkpoint, receiptQueue)
f.rawStreams[1].sharedWitness = siblingWitness
f.streamPacers[1].sharedWitness = siblingWitness
siblingFinished := make(chan struct{})
siblingSlow := f.run(func() error {
defer close(siblingFinished)
return writeFeedbackPayload(f.serverStreams[1], slowBytes)
})
if _, err := siblingWitness.awaitRaw(f.ctx, siblingFinished, 1, 128<<10); err != nil {
if _, err := siblingWitness.awaitRaw(f.ctx, siblingFinished, 1, siblingWarmupBytes); err != nil {
t.Fatalf("sibling fresh backpressure before shared reads: %v", err)
}
if _, err := primaryWitness.awaitRaw(f.ctx, primaryFinished, 1, 1<<20); err != nil {
t.Fatalf("primary first Write did not remain blocked: %v", err)
}
var after int64
for round := 0; round < rounds; round++ {
timer := time.NewTimer(120 * time.Millisecond)
select {
case <-timer.C:
case <-f.ctx.Done():
timer.Stop()
t.Fatal(f.ctx.Err())
}
for _, reader := range f.clientStreams {
if err := readFeedbackPayload(reader, feedbackChunkSize); err != nil {
t.Fatal(err)
// Release credit gradually instead of draining two 32 KiB bursts. A
// scheduler gap between those bursts can create a high-rate sample
// just over the controller's 10 ms minimum window, even though the
// round's sustained rate is low. Its recent-maximum estimator is
// right to retain that evidence. Small reads and the fixed window bound
// that burstiness without changing the production estimator or the
// prescribed checkpoint. Relative delays never catch up after a stall.
// Each read spans whole repetitions of the 32-byte payload pattern.
const readSize = 1 << 10
const steps = feedbackChunkSize / readSize
for step := 0; step < steps; step++ {
feedbackReadDelay(t, f.ctx, 120*time.Millisecond/steps)
for index, reader := range f.clientStreams {
if step == 0 && index == 1 && readerSkew != 0 {
feedbackReadDelay(t, f.ctx, readerSkew)
}
if err := readFeedbackPayload(reader, readSize); err != nil {
t.Fatal(err)
}
}
}
if round+1 == checkpoint {
Expand Down Expand Up @@ -171,6 +193,17 @@ func TestQUICPacingSharedStreamsAcceptTransportBackpressure(t *testing.T) {
before, after, transport, pacing, f.clock.sleepsWithPrimaryWrite.Load())
}

func feedbackReadDelay(t *testing.T, ctx context.Context, duration time.Duration) {
t.Helper()
timer := time.NewTimer(duration)
defer timer.Stop()
select {
case <-timer.C:
case <-ctx.Done():
t.Fatal(ctx.Err())
}
}

type feedbackQUICFixture struct {
ctx context.Context
pacer *connectionPacer
Expand Down Expand Up @@ -336,7 +369,7 @@ func writeFeedbackPayload(writer io.Writer, size int) error {
}

func readFeedbackPayload(reader io.Reader, size int) error {
buffer := make([]byte, len(feedbackPayload))
buffer := make([]byte, min(size, len(feedbackPayload)))
for remaining := size; remaining > 0; {
chunk := buffer[:min(remaining, len(buffer))]
if _, err := io.ReadFull(reader, chunk); err != nil {
Expand Down
Loading