test(brokeripc): pin client pipe headroom for a full output window
- commit
- e8bf3a5f978e34ebe4f009f1da39db77b48c6411
- author
- brice · · 2026-10-08 10:38 UTC
- parent
- 4fda349
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +25 -2
M internal/adapters/brokeripc/pipe_test.go +22 -0
| @@ -6,6 +6,8 @@ import ( | |||
| 6 | 6 | "io" | |
| 7 | 7 | "testing" | |
| 8 | 8 | ||
| 9 | + | "github.com/bnema/vev/internal/adapters/brokerwire" | |
| 10 | + | "github.com/bnema/vev/internal/protocol" | |
| 9 | 11 | "github.com/stretchr/testify/require" | |
| 10 | 12 | ) | |
| 11 | 13 | ||
| @@ -106,3 +108,23 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) { | |||
| 106 | 108 | }) | |
| 107 | 109 | } | |
| 108 | 110 | } | |
| 111 | + | ||
| 112 | + | // TestStreamPipeHoldsAFullOutputWindow pins the client pipe headroom for the | |
| 113 | + | // session output window. A client claims protocol.MaxOutputWindow unacked | |
| 114 | + | // Output frames; with a terminal that is not draining, all of them may sit in | |
| 115 | + | // this pipe at once. Overflow fails the whole stream (ErrStreamBackpressure), | |
| 116 | + | // so the default bounds must hold that window of worst realistic frames: a | |
| 117 | + | // near-full repaint of a 500x140 terminal is about 70 KB, i.e. two chunks. | |
| 118 | + | func TestStreamPipeHoldsAFullOutputWindow(t *testing.T) { | |
| 119 | + | const frameBytes = 72 << 10 | |
| 120 | + | chunk := int(brokerwire.MaxStreamChunkBytes) | |
| 121 | + | p := newStreamPipe(0, 0, func([]byte) error { return nil }) | |
| 122 | + | payload := bytes.Repeat([]byte{'x'}, frameBytes) | |
| 123 | + | for frame := range protocol.MaxOutputWindow { | |
| 124 | + | for data := payload; len(data) > 0; { | |
| 125 | + | n := min(len(data), chunk) | |
| 126 | + | require.NoError(t, p.deliver(data[:n]), "frame %d overflowed the client pipe", frame) | |
| 127 | + | data = data[n:] | |
| 128 | + | } | |
| 129 | + | } | |
| 130 | + | } | |
| @@ -6,6 +6,8 @@ import ( | |||||
| 6 | "io" | 6 | "io" | ||
| 7 | "testing" | 7 | "testing" | ||
| 8 | 8 | ||||
| 9 | + | "github.com/bnema/vev/internal/adapters/brokerwire" | |||
| 10 | + | "github.com/bnema/vev/internal/protocol" | |||
| 9 | "github.com/stretchr/testify/require" | 11 | "github.com/stretchr/testify/require" | ||
| 10 | ) | 12 | ) | ||
| 11 | 13 | ||||
| @@ -106,3 +108,23 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) { | |||||
| 106 | }) | 108 | }) | ||
| 107 | } | 109 | } | ||
| 108 | } | 110 | } | ||
| 111 | + | ||||
| 112 | + | // TestStreamPipeHoldsAFullOutputWindow pins the client pipe headroom for the | |||
| 113 | + | // session output window. A client claims protocol.MaxOutputWindow unacked | |||
| 114 | + | // Output frames; with a terminal that is not draining, all of them may sit in | |||
| 115 | + | // this pipe at once. Overflow fails the whole stream (ErrStreamBackpressure), | |||
| 116 | + | // so the default bounds must hold that window of worst realistic frames: a | |||
| 117 | + | // near-full repaint of a 500x140 terminal is about 70 KB, i.e. two chunks. | |||
| 118 | + | func TestStreamPipeHoldsAFullOutputWindow(t *testing.T) { | |||
| 119 | + | const frameBytes = 72 << 10 | |||
| 120 | + | chunk := int(brokerwire.MaxStreamChunkBytes) | |||
| 121 | + | p := newStreamPipe(0, 0, func([]byte) error { return nil }) | |||
| 122 | + | payload := bytes.Repeat([]byte{'x'}, frameBytes) | |||
| 123 | + | for frame := range protocol.MaxOutputWindow { | |||
| 124 | + | for data := payload; len(data) > 0; { | |||
| 125 | + | n := min(len(data), chunk) | |||
| 126 | + | require.NoError(t, p.deliver(data[:n]), "frame %d overflowed the client pipe", frame) | |||
| 127 | + | data = data[n:] | |||
| 128 | + | } | |||
| 129 | + | } | |||
| 130 | + | } | |||
M internal/usecase/client/runtime_primitives_test.go +3 -2
| @@ -35,8 +35,9 @@ func TestRequestedOutputWindow(t *testing.T) { | |||
| 35 | 35 | } | |
| 36 | 36 | } | |
| 37 | 37 | ||
| 38 | - | // TestHelloRequestsPipelinedOutput pins the window a real attachment Hello | |
| 39 | - | // asks for. A window of one would make every remote frame wait a round trip. | |
| 38 | + | // TestHelloRequestsPipelinedOutput pins the fallback window an attachment | |
| 39 | + | // Hello claims when its carriage states no preference. A window of one would | |
| 40 | + | // make every remote frame wait a round trip. | |
| 40 | 41 | func TestHelloRequestsPipelinedOutput(t *testing.T) { | |
| 41 | 42 | worker, err := newSessionAttachmentWorker(sessionAttachmentConfig{Request: sessionTestRequest(false), SessionEnvironment: SessionEnvironment{Provenance: SessionEnvironmentRemote}}) | |
| 42 | 43 | require.NoError(t, err) | |
| @@ -35,8 +35,9 @@ func TestRequestedOutputWindow(t *testing.T) { | |||||
| 35 | } | 35 | } | ||
| 36 | } | 36 | } | ||
| 37 | 37 | ||||
| 38 | - | // TestHelloRequestsPipelinedOutput pins the window a real attachment Hello | 38 | + | // TestHelloRequestsPipelinedOutput pins the fallback window an attachment |
| 39 | - | // asks for. A window of one would make every remote frame wait a round trip. | 39 | + | // Hello claims when its carriage states no preference. A window of one would |
| 40 | + | // make every remote frame wait a round trip. | |||
| 40 | func TestHelloRequestsPipelinedOutput(t *testing.T) { | 41 | func TestHelloRequestsPipelinedOutput(t *testing.T) { | ||
| 41 | worker, err := newSessionAttachmentWorker(sessionAttachmentConfig{Request: sessionTestRequest(false), SessionEnvironment: SessionEnvironment{Provenance: SessionEnvironmentRemote}}) | 42 | worker, err := newSessionAttachmentWorker(sessionAttachmentConfig{Request: sessionTestRequest(false), SessionEnvironment: SessionEnvironment{Provenance: SessionEnvironmentRemote}}) | ||
| 42 | require.NoError(t, err) | 43 | require.NoError(t, err) | ||