detached a054d45

Merge pull request 'perf(remote): end-to-end Output flow control (daemon byte budget + broker stream credit)' (#21) from perf/output-flow-control into main

Reviewed-on: https://git.bnema.dev/bnema/vev/pulls/21
commit
a054d45754e2f4c7b637b1953688c482938b02eb
author
bnema · · 2026-10-08 15:19 UTC
merge of
3a23d13 + ff9bba9 · diff against the first parent
signature
unsigned
26 files · +1089 -429
M docs/architecture.md +2 -2
@@ -1,6 +1,6 @@
11 # Architecture
22
3-vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 65.
3+vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 66.
44
55 `CommandResult` has a closed explicit outcome contract:
66
@@ -135,7 +135,7 @@ Use cases exchange `protocol.ClientMessage` and `protocol.ServerMessage` values.
135135 `go tool buf generate`, and handle the new variant in `sessionwire`.
136136 Never edit generated `*.pb.go`; never add manual IDs or dispatch tables.
137137 - Implement I/O, queues, workers, environment integration, or technology selection in an adapter or `internal/app`.
138-- Bump `internal/protocol.Version` for negotiated wire layout changes (currently `65`). The preamble epoch (`wire.ProtocolEpoch`, QUIC ALPN `vev/1`) bumps only for an intentional clean break.
138+- Bump `internal/protocol.Version` for negotiated wire layout changes (currently `66`). The preamble epoch (`wire.ProtocolEpoch`, QUIC ALPN `vev/1`) bumps only for an intentional clean break.
139139
140140 ## Broker
141141
@@ -1,6 +1,6 @@
1 # Architecture1 # Architecture
2 2
3-vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 65.3+vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 66.
4 4
5 `CommandResult` has a closed explicit outcome contract:5 `CommandResult` has a closed explicit outcome contract:
6 6
@@ -135,7 +135,7 @@ Use cases exchange `protocol.ClientMessage` and `protocol.ServerMessage` values.
135 `go tool buf generate`, and handle the new variant in `sessionwire`.135 `go tool buf generate`, and handle the new variant in `sessionwire`.
136 Never edit generated `*.pb.go`; never add manual IDs or dispatch tables.136 Never edit generated `*.pb.go`; never add manual IDs or dispatch tables.
137 - Implement I/O, queues, workers, environment integration, or technology selection in an adapter or `internal/app`.137 - Implement I/O, queues, workers, environment integration, or technology selection in an adapter or `internal/app`.
138-- Bump `internal/protocol.Version` for negotiated wire layout changes (currently `65`). The preamble epoch (`wire.ProtocolEpoch`, QUIC ALPN `vev/1`) bumps only for an intentional clean break.138+- Bump `internal/protocol.Version` for negotiated wire layout changes (currently `66`). The preamble epoch (`wire.ProtocolEpoch`, QUIC ALPN `vev/1`) bumps only for an intentional clean break.
139 139
140 ## Broker140 ## Broker
141 141
M docs/protocol.md +19 -3
@@ -109,8 +109,8 @@ Timeout or reply loss after dispatch is outcome unknown and is never replayed.
109109 ## Broker negotiation
110110
111111 Broker connections are a separate conversation from the session protocol.
112-`internal/adapters/brokerwire` owns it: client tags 101-111 and server tags
113-201-209 are disjoint from every session tag, and each direction is its own
112+`internal/adapters/brokerwire` owns it: client tags 101-114 and server tags
113+201-211 are disjoint from every session tag, and each direction is its own
114114 closed directional `oneof` union. A broker connection opens with exactly one
115115 broker preamble (`PreambleRequest` client → server, `PreambleResponse` server
116116 → client) using broker roles 3/4. The preamble carries the same magic
@@ -127,7 +127,15 @@ connection dispatcher.
127127
128128 `internal/adapters/brokeripc` carries this conversation over `broker.sock`
129129 in the per-user runtime directory, with same-user peer-credential admission
130-and bounded per-connection queues. The remote helpers `_broker-mux-stdio`,
130+and bounded per-connection queues. Each logical stream is credit flow
131+controlled in both directions: a sender spends `StreamChunkCredit` (payload
132+plus 64 bytes) per data frame out of a 4 MiB window
133+(`brokerwire.StreamWindowBytes`) and waits when it has too little, and the
134+receiver returns the credit of frames its consumer has read in
135+`StreamWindowUpdate` batches. A slow consumer therefore slows its own stream
136+instead of losing it. Data or cumulative credit beyond the window is a
137+flow-control violation that settles only that stream; a single grant of zero
138+or above the window is a malformed frame and fails the connection. The remote helpers `_broker-mux-stdio`,
131139 `_broker-mux-quic-bootstrap`, and `_broker-mux-quic-proxy` bridge a remote
132140 daemonmux socket to stdio or to one freshly authenticated QUIC stream.
133141
@@ -162,6 +170,12 @@ base. `ValidateOutput` enforces the epoch/base/new chain
162170 claims its carriage's preferred window, or `MaxOutputWindow` when the
163171 carriage states none, so output pipelines instead of waiting a round trip
164172 per frame.
173+- Unacknowledged Output data is also bounded by `MaxOutputWindowBytes`
174+ (2 MiB). State frames are charged to their own state, side effects to the
175+ next state, and a cumulative ACK releases what it covers. The next state
176+ frame waits when either window is full, except that a frame may always be
177+ sent while no state frame is in flight. A frame carrying a large image may
178+ exceed the byte window; it then holds the next frame until its ACK.
165179 - The client coalesces ACKs (`client.go:cumulativeAckQueue`) and keeps
166180 applying + acknowledging daemon frames even while a picker lease owns
167181 the terminal (see below) — admission without physical write.
@@ -247,6 +261,8 @@ than `KillResult` and `CommandResult`.
247261 | Queued envelopes per connection | 8 messages, 32 MiB bytes (`streamframe` defaults), then backpressure; sync sends uncharged, async charged once | `adapters/streamframe` |
248262 | Encoding buffer | bounded per-connection framing buffers owned by `streamframe` | `adapters/streamframe` |
249263 | Output states in flight | 8 (`MaxOutputWindow`) preferred for QUIC/IPC/SSH | daemon, negotiated down |
264+| Unacknowledged Output bytes | 2 MiB (`MaxOutputWindowBytes`); one frame always allowed when none is in flight | daemon |
265+| Broker stream window | 4 MiB per stream and direction (`brokerwire.StreamWindowBytes`), returned by `StreamWindowUpdate` | `adapters/brokerwire`, enforced in `adapters/brokeripc` |
250266 | QUIC unauthenticated peers | 4 connections, 3 s auth deadline, one bidirectional stream each; extra/unidirectional streams rejected | `adapters/quic` |
251267 | QUIC bootstrap | 32-byte token + 16-byte nonce (base64), ≤4 KiB readiness (schema v1, host-independent port, SHA-256 pin, ≤15 s expiry), one bounded (512 B) auth record, atomic one-time consumption, token erasure on close | `adapters/quic` bootstrap, `app/_broker-mux-quic-bootstrap` + `_broker-mux-quic-proxy` |
252268 | Handshake, dial → first committed publication | 15 s, single absolute deadline propagated unchanged, never reset | both ends |
@@ -109,8 +109,8 @@ Timeout or reply loss after dispatch is outcome unknown and is never replayed.
109 ## Broker negotiation109 ## Broker negotiation
110 110
111 Broker connections are a separate conversation from the session protocol.111 Broker connections are a separate conversation from the session protocol.
112-`internal/adapters/brokerwire` owns it: client tags 101-111 and server tags112+`internal/adapters/brokerwire` owns it: client tags 101-114 and server tags
113-201-209 are disjoint from every session tag, and each direction is its own113+201-211 are disjoint from every session tag, and each direction is its own
114 closed directional `oneof` union. A broker connection opens with exactly one114 closed directional `oneof` union. A broker connection opens with exactly one
115 broker preamble (`PreambleRequest` client → server, `PreambleResponse` server115 broker preamble (`PreambleRequest` client → server, `PreambleResponse` server
116 → client) using broker roles 3/4. The preamble carries the same magic116 → client) using broker roles 3/4. The preamble carries the same magic
@@ -127,7 +127,15 @@ connection dispatcher.
127 127
128 `internal/adapters/brokeripc` carries this conversation over `broker.sock`128 `internal/adapters/brokeripc` carries this conversation over `broker.sock`
129 in the per-user runtime directory, with same-user peer-credential admission129 in the per-user runtime directory, with same-user peer-credential admission
130-and bounded per-connection queues. The remote helpers `_broker-mux-stdio`,130+and bounded per-connection queues. Each logical stream is credit flow
131+controlled in both directions: a sender spends `StreamChunkCredit` (payload
132+plus 64 bytes) per data frame out of a 4 MiB window
133+(`brokerwire.StreamWindowBytes`) and waits when it has too little, and the
134+receiver returns the credit of frames its consumer has read in
135+`StreamWindowUpdate` batches. A slow consumer therefore slows its own stream
136+instead of losing it. Data or cumulative credit beyond the window is a
137+flow-control violation that settles only that stream; a single grant of zero
138+or above the window is a malformed frame and fails the connection. The remote helpers `_broker-mux-stdio`,
131 `_broker-mux-quic-bootstrap`, and `_broker-mux-quic-proxy` bridge a remote139 `_broker-mux-quic-bootstrap`, and `_broker-mux-quic-proxy` bridge a remote
132 daemonmux socket to stdio or to one freshly authenticated QUIC stream.140 daemonmux socket to stdio or to one freshly authenticated QUIC stream.
133 141
@@ -162,6 +170,12 @@ base. `ValidateOutput` enforces the epoch/base/new chain
162 claims its carriage's preferred window, or `MaxOutputWindow` when the170 claims its carriage's preferred window, or `MaxOutputWindow` when the
163 carriage states none, so output pipelines instead of waiting a round trip171 carriage states none, so output pipelines instead of waiting a round trip
164 per frame.172 per frame.
173+- Unacknowledged Output data is also bounded by `MaxOutputWindowBytes`
174+ (2 MiB). State frames are charged to their own state, side effects to the
175+ next state, and a cumulative ACK releases what it covers. The next state
176+ frame waits when either window is full, except that a frame may always be
177+ sent while no state frame is in flight. A frame carrying a large image may
178+ exceed the byte window; it then holds the next frame until its ACK.
165 - The client coalesces ACKs (`client.go:cumulativeAckQueue`) and keeps179 - The client coalesces ACKs (`client.go:cumulativeAckQueue`) and keeps
166 applying + acknowledging daemon frames even while a picker lease owns180 applying + acknowledging daemon frames even while a picker lease owns
167 the terminal (see below) — admission without physical write.181 the terminal (see below) — admission without physical write.
@@ -247,6 +261,8 @@ than `KillResult` and `CommandResult`.
247 | Queued envelopes per connection | 8 messages, 32 MiB bytes (`streamframe` defaults), then backpressure; sync sends uncharged, async charged once | `adapters/streamframe` |261 | Queued envelopes per connection | 8 messages, 32 MiB bytes (`streamframe` defaults), then backpressure; sync sends uncharged, async charged once | `adapters/streamframe` |
248 | Encoding buffer | bounded per-connection framing buffers owned by `streamframe` | `adapters/streamframe` |262 | Encoding buffer | bounded per-connection framing buffers owned by `streamframe` | `adapters/streamframe` |
249 | Output states in flight | 8 (`MaxOutputWindow`) preferred for QUIC/IPC/SSH | daemon, negotiated down |263 | Output states in flight | 8 (`MaxOutputWindow`) preferred for QUIC/IPC/SSH | daemon, negotiated down |
264+| Unacknowledged Output bytes | 2 MiB (`MaxOutputWindowBytes`); one frame always allowed when none is in flight | daemon |
265+| Broker stream window | 4 MiB per stream and direction (`brokerwire.StreamWindowBytes`), returned by `StreamWindowUpdate` | `adapters/brokerwire`, enforced in `adapters/brokeripc` |
250 | QUIC unauthenticated peers | 4 connections, 3 s auth deadline, one bidirectional stream each; extra/unidirectional streams rejected | `adapters/quic` |266 | QUIC unauthenticated peers | 4 connections, 3 s auth deadline, one bidirectional stream each; extra/unidirectional streams rejected | `adapters/quic` |
251 | QUIC bootstrap | 32-byte token + 16-byte nonce (base64), ≤4 KiB readiness (schema v1, host-independent port, SHA-256 pin, ≤15 s expiry), one bounded (512 B) auth record, atomic one-time consumption, token erasure on close | `adapters/quic` bootstrap, `app/_broker-mux-quic-bootstrap` + `_broker-mux-quic-proxy` |267 | QUIC bootstrap | 32-byte token + 16-byte nonce (base64), ≤4 KiB readiness (schema v1, host-independent port, SHA-256 pin, ≤15 s expiry), one bounded (512 B) auth record, atomic one-time consumption, token erasure on close | `adapters/quic` bootstrap, `app/_broker-mux-quic-bootstrap` + `_broker-mux-quic-proxy` |
252 | Handshake, dial → first committed publication | 15 s, single absolute deadline propagated unchanged, never reset | both ends |268 | Handshake, dial → first committed publication | 15 s, single absolute deadline propagated unchanged, never reset | both ends |
M internal/adapters/brokeripc/client.go +30 -14
@@ -351,23 +351,24 @@ func (c *client) dispatch(message brokerwire.ServerMessage) error {
351351 if !c.scopeMatches(m.Epoch, m.Connection) {
352352 return errors.Join(ErrScopeMismatch, ErrProtocol)
353353 }
354- disposition, err := c.conn.StreamData(m.Stream)
355- if err != nil {
356- return errors.Join(ErrProtocol, err)
354+ st, err := c.streamFor(m.Stream)
355+ if st == nil || err != nil {
356+ return err
357357 }
358- if disposition == brokerwire.StreamDiscarded {
359- return nil
358+ if err := st.deliver(m.Data); errors.Is(err, ErrStreamCredit) {
359+ st.fail(ErrStreamCredit)
360360 }
361- st := c.lookupStream(m.Stream)
362- if st == nil {
363- // The stream was retired locally between the tracker check and the
364- // lookup; its data is discarded, exactly like a retired stream.
365- return nil
361+ return nil
362+ case brokerwire.StreamWindowUpdate:
363+ if !c.scopeMatches(m.Epoch, m.Connection) {
364+ return errors.Join(ErrScopeMismatch, ErrProtocol)
366365 }
367- if err := st.deliver(m.Data); err != nil {
368- if errors.Is(err, ErrStreamBackpressure) {
369- st.fail(ErrStreamBackpressure)
370- }
366+ st, err := c.streamFor(m.Stream)
367+ if st == nil || err != nil {
368+ return err
369+ }
370+ if err := st.pipe.granted(m.Credit); err != nil {
371+ st.fail(err)
371372 }
372373 return nil
373374 case brokerwire.StreamClosed:
@@ -926,6 +927,21 @@ func (c *client) lookupStream(stream ports.BrokerStreamID) *clientStream {
926927 return c.streams[stream]
927928 }
928929
930+// streamFor classifies one stream-scoped frame (data or a credit grant) and
931+// returns its live stream. A frame for a retired stream, or one retired locally
932+// between the tracker check and the lookup, yields no stream and no error; a
933+// frame for a stream that was never opened is a peer protocol violation.
934+func (c *client) streamFor(id ports.BrokerStreamID) (*clientStream, error) {
935+ disposition, err := c.conn.StreamData(id)
936+ if err != nil {
937+ return nil, errors.Join(ErrProtocol, err)
938+ }
939+ if disposition == brokerwire.StreamDiscarded {
940+ return nil, nil
941+ }
942+ return c.lookupStream(id), nil
943+}
944+
929945 // unregisterStream drops one local stream record.
930946 func (c *client) unregisterStream(stream ports.BrokerStreamID) {
931947 c.mu.Lock()
@@ -351,23 +351,24 @@ func (c *client) dispatch(message brokerwire.ServerMessage) error {
351 if !c.scopeMatches(m.Epoch, m.Connection) {351 if !c.scopeMatches(m.Epoch, m.Connection) {
352 return errors.Join(ErrScopeMismatch, ErrProtocol)352 return errors.Join(ErrScopeMismatch, ErrProtocol)
353 }353 }
354- disposition, err := c.conn.StreamData(m.Stream)354+ st, err := c.streamFor(m.Stream)
355- if err != nil {355+ if st == nil || err != nil {
356- return errors.Join(ErrProtocol, err)356+ return err
357 }357 }
358- if disposition == brokerwire.StreamDiscarded {358+ if err := st.deliver(m.Data); errors.Is(err, ErrStreamCredit) {
359- return nil359+ st.fail(ErrStreamCredit)
360 }360 }
361- st := c.lookupStream(m.Stream)361+ return nil
362- if st == nil {362+ case brokerwire.StreamWindowUpdate:
363- // The stream was retired locally between the tracker check and the363+ if !c.scopeMatches(m.Epoch, m.Connection) {
364- // lookup; its data is discarded, exactly like a retired stream.364+ return errors.Join(ErrScopeMismatch, ErrProtocol)
365- return nil
366 }365 }
367- if err := st.deliver(m.Data); err != nil {366+ st, err := c.streamFor(m.Stream)
368- if errors.Is(err, ErrStreamBackpressure) {367+ if st == nil || err != nil {
369- st.fail(ErrStreamBackpressure)368+ return err
370- }369+ }
370+ if err := st.pipe.granted(m.Credit); err != nil {
371+ st.fail(err)
371 }372 }
372 return nil373 return nil
373 case brokerwire.StreamClosed:374 case brokerwire.StreamClosed:
@@ -926,6 +927,21 @@ func (c *client) lookupStream(stream ports.BrokerStreamID) *clientStream {
926 return c.streams[stream]927 return c.streams[stream]
927 }928 }
928 929
930+// streamFor classifies one stream-scoped frame (data or a credit grant) and
931+// returns its live stream. A frame for a retired stream, or one retired locally
932+// between the tracker check and the lookup, yields no stream and no error; a
933+// frame for a stream that was never opened is a peer protocol violation.
934+func (c *client) streamFor(id ports.BrokerStreamID) (*clientStream, error) {
935+ disposition, err := c.conn.StreamData(id)
936+ if err != nil {
937+ return nil, errors.Join(ErrProtocol, err)
938+ }
939+ if disposition == brokerwire.StreamDiscarded {
940+ return nil, nil
941+ }
942+ return c.lookupStream(id), nil
943+}
944+
929 // unregisterStream drops one local stream record.945 // unregisterStream drops one local stream record.
930 func (c *client) unregisterStream(stream ports.BrokerStreamID) {946 func (c *client) unregisterStream(stream ports.BrokerStreamID) {
931 c.mu.Lock()947 c.mu.Lock()
M internal/adapters/brokeripc/config.go +1 -23
@@ -15,13 +15,6 @@ const (
1515 // listener. A listener acquires one slot before accepting, so the bound is
1616 // enforced before a socket is admitted rather than after.
1717 DefaultMaxClients = 64
18- // DefaultStreamInboundChunks bounds one logical stream's inbound chunk
19- // queue: frames already accepted from the wire and not yet consumed by the
20- // local session carriage.
21- DefaultStreamInboundChunks = 64
22- // DefaultStreamInboundBytes bounds one logical stream's inbound chunk queue
23- // in bytes (4 MiB).
24- DefaultStreamInboundBytes = 4 << 20
2518 )
2619
2720 // Config bounds one endpoint. The zero value is valid and selects every
@@ -29,10 +22,6 @@ const (
2922 type Config struct {
3023 // MaxClients bounds concurrent accepted client connections.
3124 MaxClients int
32- // StreamInboundChunks bounds one logical stream's inbound chunk queue.
33- StreamInboundChunks int
34- // StreamInboundBytes bounds one logical stream's inbound chunk bytes.
35- StreamInboundBytes uint64
3625 // HandshakeTimeout bounds one accepted connection's broker preamble, broker
3726 // admission, and the wait for its Register. The client dialer also bounds
3827 // its whole setup (Unix dial, preamble, Register send, and wait for
@@ -62,12 +51,6 @@ func (c Config) withDefaults() Config {
6251 if c.MaxClients <= 0 {
6352 c.MaxClients = DefaultMaxClients
6453 }
65- if c.StreamInboundChunks <= 0 {
66- c.StreamInboundChunks = DefaultStreamInboundChunks
67- }
68- if c.StreamInboundBytes == 0 {
69- c.StreamInboundBytes = DefaultStreamInboundBytes
70- }
7154 if c.HandshakeTimeout <= 0 {
7255 c.HandshakeTimeout = protocol.HandshakeTimeout
7356 }
@@ -80,16 +63,11 @@ func (c Config) withDefaults() Config {
8063 // validate refuses a configuration whose explicit bounds cannot be honored.
8164 // The zero value is always valid because withDefaults has already been applied.
8265 func (c Config) validate() error {
83- if c.MaxClients <= 0 || c.StreamInboundChunks <= 0 || c.StreamInboundBytes == 0 || c.HandshakeTimeout <= 0 {
66+ if c.MaxClients <= 0 || c.HandshakeTimeout <= 0 {
8467 return ErrConfig
8568 }
8669 if c.MaxPendingOperations <= 0 || c.MaxPendingOperations > brokerwire.MaxPendingOperations {
8770 return ErrConfig
8871 }
89- // A stream inbound byte bound below one maximum chunk can never accept a
90- // full stream frame, which would starve a legal peer.
91- if c.StreamInboundBytes < brokerwire.MaxStreamChunkBytes {
92- return ErrConfig
93- }
9472 return nil
9573 }
@@ -15,13 +15,6 @@ const (
15 // listener. A listener acquires one slot before accepting, so the bound is15 // listener. A listener acquires one slot before accepting, so the bound is
16 // enforced before a socket is admitted rather than after.16 // enforced before a socket is admitted rather than after.
17 DefaultMaxClients = 6417 DefaultMaxClients = 64
18- // DefaultStreamInboundChunks bounds one logical stream's inbound chunk
19- // queue: frames already accepted from the wire and not yet consumed by the
20- // local session carriage.
21- DefaultStreamInboundChunks = 64
22- // DefaultStreamInboundBytes bounds one logical stream's inbound chunk queue
23- // in bytes (4 MiB).
24- DefaultStreamInboundBytes = 4 << 20
25 )18 )
26 19
27 // Config bounds one endpoint. The zero value is valid and selects every20 // Config bounds one endpoint. The zero value is valid and selects every
@@ -29,10 +22,6 @@ const (
29 type Config struct {22 type Config struct {
30 // MaxClients bounds concurrent accepted client connections.23 // MaxClients bounds concurrent accepted client connections.
31 MaxClients int24 MaxClients int
32- // StreamInboundChunks bounds one logical stream's inbound chunk queue.
33- StreamInboundChunks int
34- // StreamInboundBytes bounds one logical stream's inbound chunk bytes.
35- StreamInboundBytes uint64
36 // HandshakeTimeout bounds one accepted connection's broker preamble, broker25 // HandshakeTimeout bounds one accepted connection's broker preamble, broker
37 // admission, and the wait for its Register. The client dialer also bounds26 // admission, and the wait for its Register. The client dialer also bounds
38 // its whole setup (Unix dial, preamble, Register send, and wait for27 // its whole setup (Unix dial, preamble, Register send, and wait for
@@ -62,12 +51,6 @@ func (c Config) withDefaults() Config {
62 if c.MaxClients <= 0 {51 if c.MaxClients <= 0 {
63 c.MaxClients = DefaultMaxClients52 c.MaxClients = DefaultMaxClients
64 }53 }
65- if c.StreamInboundChunks <= 0 {
66- c.StreamInboundChunks = DefaultStreamInboundChunks
67- }
68- if c.StreamInboundBytes == 0 {
69- c.StreamInboundBytes = DefaultStreamInboundBytes
70- }
71 if c.HandshakeTimeout <= 0 {54 if c.HandshakeTimeout <= 0 {
72 c.HandshakeTimeout = protocol.HandshakeTimeout55 c.HandshakeTimeout = protocol.HandshakeTimeout
73 }56 }
@@ -80,16 +63,11 @@ func (c Config) withDefaults() Config {
80 // validate refuses a configuration whose explicit bounds cannot be honored.63 // validate refuses a configuration whose explicit bounds cannot be honored.
81 // The zero value is always valid because withDefaults has already been applied.64 // The zero value is always valid because withDefaults has already been applied.
82 func (c Config) validate() error {65 func (c Config) validate() error {
83- if c.MaxClients <= 0 || c.StreamInboundChunks <= 0 || c.StreamInboundBytes == 0 || c.HandshakeTimeout <= 0 {66+ if c.MaxClients <= 0 || c.HandshakeTimeout <= 0 {
84 return ErrConfig67 return ErrConfig
85 }68 }
86 if c.MaxPendingOperations <= 0 || c.MaxPendingOperations > brokerwire.MaxPendingOperations {69 if c.MaxPendingOperations <= 0 || c.MaxPendingOperations > brokerwire.MaxPendingOperations {
87 return ErrConfig70 return ErrConfig
88 }71 }
89- // A stream inbound byte bound below one maximum chunk can never accept a
90- // full stream frame, which would starve a legal peer.
91- if c.StreamInboundBytes < brokerwire.MaxStreamChunkBytes {
92- return ErrConfig
93- }
94 return nil72 return nil
95 }73 }
M internal/adapters/brokeripc/errors.go +4 -4
@@ -33,10 +33,10 @@ var (
3333 // ordering: a duplicate Register, a frame for an unallocated stream, or
3434 // stream data before the stream was established.
3535 ErrProtocol = errors.New("brokeripc: broker protocol violation")
36- // ErrStreamBackpressure reports a logical stream whose local consumer is
37- // not draining its bounded inbound queue. The affected stream is settled
38- // alone; the connection and every sibling stream stay up.
39- ErrStreamBackpressure = errors.New("brokeripc: stream consumer is not keeping up")
36+ // ErrStreamCredit reports a peer that sent stream data beyond the credit it
37+ // was granted, or granted credit beyond one stream window. The affected
38+ // stream is settled alone; the connection and every sibling stream stay up.
39+ ErrStreamCredit = errors.New("brokeripc: stream flow-control violation")
4040 // ErrStreamGone reports a stream the peer retired while this side was still
4141 // starting or using it.
4242 ErrStreamGone = errors.New("brokeripc: logical stream is gone")
@@ -33,10 +33,10 @@ var (
33 // ordering: a duplicate Register, a frame for an unallocated stream, or33 // ordering: a duplicate Register, a frame for an unallocated stream, or
34 // stream data before the stream was established.34 // stream data before the stream was established.
35 ErrProtocol = errors.New("brokeripc: broker protocol violation")35 ErrProtocol = errors.New("brokeripc: broker protocol violation")
36- // ErrStreamBackpressure reports a logical stream whose local consumer is36+ // ErrStreamCredit reports a peer that sent stream data beyond the credit it
37- // not draining its bounded inbound queue. The affected stream is settled37+ // was granted, or granted credit beyond one stream window. The affected
38- // alone; the connection and every sibling stream stay up.38+ // stream is settled alone; the connection and every sibling stream stay up.
39- ErrStreamBackpressure = errors.New("brokeripc: stream consumer is not keeping up")39+ ErrStreamCredit = errors.New("brokeripc: stream flow-control violation")
40 // ErrStreamGone reports a stream the peer retired while this side was still40 // ErrStreamGone reports a stream the peer retired while this side was still
41 // starting or using it.41 // starting or using it.
42 ErrStreamGone = errors.New("brokeripc: logical stream is gone")42 ErrStreamGone = errors.New("brokeripc: logical stream is gone")
M internal/adapters/brokeripc/limits_test.go +13 -45
@@ -147,71 +147,39 @@ func TestConfigDefaultsAndValidation(t *testing.T) {
147147 cfg := Config{}.withDefaults()
148148 require.NoError(t, cfg.validate())
149149 require.Equal(t, DefaultMaxClients, cfg.MaxClients)
150- require.Equal(t, DefaultStreamInboundChunks, cfg.StreamInboundChunks)
151- require.Equal(t, uint64(DefaultStreamInboundBytes), cfg.StreamInboundBytes)
152150 require.Equal(t, brokerwire.MaxPendingOperations, cfg.MaxPendingOperations)
153151
154- require.ErrorIs(t, (Config{StreamInboundBytes: 1}).withDefaults().validate(), ErrConfig)
155152 require.ErrorIs(t, (Config{MaxPendingOperations: brokerwire.MaxPendingOperations + 1}).withDefaults().validate(), ErrConfig)
156153 // A negative explicit bound is replaced by its default rather than refused.
157- require.NoError(t, (Config{MaxClients: -1, StreamInboundChunks: -1}).withDefaults().validate())
154+ require.NoError(t, (Config{MaxClients: -1}).withDefaults().validate())
158155 }
159156
160-// TestStreamPipeBoundsAndClose proves the per-stream byte channel is bounded,
161-// reports backpressure instead of growing, and releases a parked reader on
162-// close.
163-func TestStreamPipeBoundsAndClose(t *testing.T) {
164- pipe := newStreamPipe(2, 4096, func([]byte) error { return nil })
165- require.NoError(t, pipe.deliver([]byte("one")))
166- require.NoError(t, pipe.deliver([]byte("two")))
167- require.ErrorIs(t, pipe.deliver([]byte("three")), ErrStreamBackpressure)
168-
169- buf := make([]byte, 3)
170- n, err := pipe.Read(buf)
171- require.NoError(t, err)
172- require.Equal(t, "one", string(buf[:n]))
173- require.NoError(t, pipe.deliver([]byte("three")))
174-
175- // An orderly Close keeps what is already queued ("two" and "three"): Read
176- // drains it before observing io.EOF, so a chunk delivered just ahead of an
177- // orderly close is never discarded underneath its reader (see
178- // TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose for the focused
179- // coverage of this behavior).
180- require.NoError(t, pipe.Close())
181- require.ErrorIs(t, pipe.deliver([]byte("four")), ErrStreamGone)
182- drained := make([]byte, 8)
183- n, err = pipe.Read(drained)
184- require.NoError(t, err)
185- require.Equal(t, "two", string(drained[:n]))
186- n, err = pipe.Read(drained)
187- require.NoError(t, err)
188- require.Equal(t, "three", string(drained[:n]))
189- _, err = pipe.Read(drained)
190- require.ErrorIs(t, err, io.EOF)
191-
192- // A reader parked on an empty pipe is released by closeWith with its cause.
193- parked := newStreamPipe(1, 4096, func([]byte) error { return nil })
157+// TestStreamPipeCloseReleasesParkedReader proves a reader parked on an empty
158+// pipe is released by closeWith with its cause.
159+func TestStreamPipeCloseReleasesParkedReader(t *testing.T) {
160+ parked := newTestPipe()
194161 done := make(chan error, 1)
195162 go func() {
196- _, err := parked.Read(buf)
163+ _, err := parked.Read(make([]byte, 3))
197164 done <- err
198165 }()
199- time.Sleep(20 * time.Millisecond)
200- parked.closeWith(ErrStreamBackpressure)
166+ parked.closeWith(ErrStreamCredit)
201167 select {
202168 case err := <-done:
203- require.ErrorIs(t, err, ErrStreamBackpressure)
169+ require.ErrorIs(t, err, ErrStreamCredit)
204170 case <-time.After(5 * time.Second):
205171 t.Fatal("closeWith must release a parked reader")
206172 }
173+ require.ErrorIs(t, parked.deliver([]byte("late")), ErrStreamGone)
207174 }
208175
209-// TestStreamPipeOutboundError proves a failing outbound hook surfaces instead of
176+// TestStreamPipeOutboundError proves a failing sendFrame hook surfaces instead of
210177 // silently dropping bytes.
211178 func TestStreamPipeOutboundError(t *testing.T) {
212- pipe := newStreamPipe(1, 4096, func([]byte) error { return ErrStreamBackpressure })
179+ failure := errors.New("outbound failed")
180+ pipe := newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return failure }, func(uint64) error { return nil })
213181 _, err := pipe.Write([]byte("payload"))
214- require.ErrorIs(t, err, ErrStreamBackpressure)
182+ require.ErrorIs(t, err, failure)
215183 require.NoError(t, pipe.Close())
216184 _, err = pipe.Write([]byte("payload"))
217185 require.ErrorIs(t, err, ErrStreamGone)
@@ -147,71 +147,39 @@ func TestConfigDefaultsAndValidation(t *testing.T) {
147 cfg := Config{}.withDefaults()147 cfg := Config{}.withDefaults()
148 require.NoError(t, cfg.validate())148 require.NoError(t, cfg.validate())
149 require.Equal(t, DefaultMaxClients, cfg.MaxClients)149 require.Equal(t, DefaultMaxClients, cfg.MaxClients)
150- require.Equal(t, DefaultStreamInboundChunks, cfg.StreamInboundChunks)
151- require.Equal(t, uint64(DefaultStreamInboundBytes), cfg.StreamInboundBytes)
152 require.Equal(t, brokerwire.MaxPendingOperations, cfg.MaxPendingOperations)150 require.Equal(t, brokerwire.MaxPendingOperations, cfg.MaxPendingOperations)
153 151
154- require.ErrorIs(t, (Config{StreamInboundBytes: 1}).withDefaults().validate(), ErrConfig)
155 require.ErrorIs(t, (Config{MaxPendingOperations: brokerwire.MaxPendingOperations + 1}).withDefaults().validate(), ErrConfig)152 require.ErrorIs(t, (Config{MaxPendingOperations: brokerwire.MaxPendingOperations + 1}).withDefaults().validate(), ErrConfig)
156 // A negative explicit bound is replaced by its default rather than refused.153 // A negative explicit bound is replaced by its default rather than refused.
157- require.NoError(t, (Config{MaxClients: -1, StreamInboundChunks: -1}).withDefaults().validate())154+ require.NoError(t, (Config{MaxClients: -1}).withDefaults().validate())
158 }155 }
159 156
160-// TestStreamPipeBoundsAndClose proves the per-stream byte channel is bounded,157+// TestStreamPipeCloseReleasesParkedReader proves a reader parked on an empty
161-// reports backpressure instead of growing, and releases a parked reader on158+// pipe is released by closeWith with its cause.
162-// close.159+func TestStreamPipeCloseReleasesParkedReader(t *testing.T) {
163-func TestStreamPipeBoundsAndClose(t *testing.T) {160+ parked := newTestPipe()
164- pipe := newStreamPipe(2, 4096, func([]byte) error { return nil })
165- require.NoError(t, pipe.deliver([]byte("one")))
166- require.NoError(t, pipe.deliver([]byte("two")))
167- require.ErrorIs(t, pipe.deliver([]byte("three")), ErrStreamBackpressure)
168-
169- buf := make([]byte, 3)
170- n, err := pipe.Read(buf)
171- require.NoError(t, err)
172- require.Equal(t, "one", string(buf[:n]))
173- require.NoError(t, pipe.deliver([]byte("three")))
174-
175- // An orderly Close keeps what is already queued ("two" and "three"): Read
176- // drains it before observing io.EOF, so a chunk delivered just ahead of an
177- // orderly close is never discarded underneath its reader (see
178- // TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose for the focused
179- // coverage of this behavior).
180- require.NoError(t, pipe.Close())
181- require.ErrorIs(t, pipe.deliver([]byte("four")), ErrStreamGone)
182- drained := make([]byte, 8)
183- n, err = pipe.Read(drained)
184- require.NoError(t, err)
185- require.Equal(t, "two", string(drained[:n]))
186- n, err = pipe.Read(drained)
187- require.NoError(t, err)
188- require.Equal(t, "three", string(drained[:n]))
189- _, err = pipe.Read(drained)
190- require.ErrorIs(t, err, io.EOF)
191-
192- // A reader parked on an empty pipe is released by closeWith with its cause.
193- parked := newStreamPipe(1, 4096, func([]byte) error { return nil })
194 done := make(chan error, 1)161 done := make(chan error, 1)
195 go func() {162 go func() {
196- _, err := parked.Read(buf)163+ _, err := parked.Read(make([]byte, 3))
197 done <- err164 done <- err
198 }()165 }()
199- time.Sleep(20 * time.Millisecond)166+ parked.closeWith(ErrStreamCredit)
200- parked.closeWith(ErrStreamBackpressure)
201 select {167 select {
202 case err := <-done:168 case err := <-done:
203- require.ErrorIs(t, err, ErrStreamBackpressure)169+ require.ErrorIs(t, err, ErrStreamCredit)
204 case <-time.After(5 * time.Second):170 case <-time.After(5 * time.Second):
205 t.Fatal("closeWith must release a parked reader")171 t.Fatal("closeWith must release a parked reader")
206 }172 }
173+ require.ErrorIs(t, parked.deliver([]byte("late")), ErrStreamGone)
207 }174 }
208 175
209-// TestStreamPipeOutboundError proves a failing outbound hook surfaces instead of176+// TestStreamPipeOutboundError proves a failing sendFrame hook surfaces instead of
210 // silently dropping bytes.177 // silently dropping bytes.
211 func TestStreamPipeOutboundError(t *testing.T) {178 func TestStreamPipeOutboundError(t *testing.T) {
212- pipe := newStreamPipe(1, 4096, func([]byte) error { return ErrStreamBackpressure })179+ failure := errors.New("outbound failed")
180+ pipe := newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return failure }, func(uint64) error { return nil })
213 _, err := pipe.Write([]byte("payload"))181 _, err := pipe.Write([]byte("payload"))
214- require.ErrorIs(t, err, ErrStreamBackpressure)182+ require.ErrorIs(t, err, failure)
215 require.NoError(t, pipe.Close())183 require.NoError(t, pipe.Close())
216 _, err = pipe.Write([]byte("payload"))184 _, err = pipe.Write([]byte("payload"))
217 require.ErrorIs(t, err, ErrStreamGone)185 require.ErrorIs(t, err, ErrStreamGone)
M internal/adapters/brokeripc/pipe.go +150 -53
@@ -3,53 +3,84 @@ package brokeripc
33 import (
44 "io"
55 "sync"
6+
7+ "github.com/bnema/vev/internal/adapters/brokerwire"
68 )
79
8-// streamPipe is the private ordered, bounded byte channel between one broker
9-// wire session and the sessionwire framing of one logical stream.
10+// streamPipe is the private ordered, credit-controlled byte channel between one
11+// broker wire session and the sessionwire framing of one logical stream.
1012 //
11-// The wire reader never blocks on a slow consumer: a chunk the peer sent is
12-// appended to a bounded queue and refused with ErrStreamBackpressure once the
13-// bound is reached, so only that stream is settled and every sibling and the
14-// physical connection stay untouched. The consumer never blocks the writer:
15-// Read serves already-queued chunks or parks on the wake channel, and Close
16-// releases both ends promptly. Write hands each byte slice to the injected
17-// outbound hook, which owns chunking it under the negotiated stream chunk
18-// ceiling and enqueueing the frames on the connection.
13+// Each direction of a stream has a brokerwire.StreamWindowBytes credit window.
14+// The sender spends brokerwire.StreamChunkCredit per data frame before writing
15+// it and waits while it has too little, so a slow consumer slows its own
16+// sender instead of failing the stream. The receiver returns the credit of
17+// every frame its consumer has fully read with one StreamWindowUpdate per
18+// batch. Because a conforming peer never exceeds the window, the wire reader
19+// never blocks: it appends each frame to the queue, and a frame beyond the
20+// granted credit is a peer violation (ErrStreamCredit) that settles only that
21+// stream. Close releases the reader, a writer waiting for credit, and the
22+// writer's peer promptly.
1923 type streamPipe struct {
20- mu sync.Mutex
21- queue [][]byte
22- queued int
23- closed bool
24- err error
25- maxChunk int
26- maxBytes int
24+ mu sync.Mutex
25+ queue []pipeChunk
26+ closed bool
27+ err error
28+
29+ // recvUsed is the credit held by queued frames, recvReturn the credit of
30+ // fully read frames not yet granted back, and returnAt the batch size.
31+ recvUsed uint64
32+ recvReturn uint64
33+ returnAt uint64
34+ // sendCredit is what the peer still allows this side to send.
35+ sendCredit uint64
36+ creditWake chan struct{}
2737
2838 wake chan struct{}
2939 closedCh chan struct{}
3040
31- outbound func([]byte) error
41+ ceiling int
42+ sendFrame func([]byte) error
43+ grant func(uint64) error
3244 }
3345
34-// newStreamPipe builds one bounded stream carriage. outbound must not be nil.
35-func newStreamPipe(maxChunks int, maxBytes uint64, outbound func([]byte) error) *streamPipe {
36- if maxChunks <= 0 {
37- maxChunks = DefaultStreamInboundChunks
38- }
39- if maxBytes == 0 {
40- maxBytes = DefaultStreamInboundBytes
41- }
46+// pipeChunk is one queued inbound frame and the credit it holds until it is
47+// fully read.
48+type pipeChunk struct {
49+ data []byte
50+ cost uint64
51+}
52+
53+// newStreamPipe builds one stream carriage. Write splits outbound bytes into
54+// frames of at most ceiling bytes, the negotiated stream chunk limit, and hands
55+// each to sendFrame once its credit is reserved; grant returns consumed inbound
56+// credit to the peer. Neither hook may be nil.
57+func newStreamPipe(ceiling int, sendFrame func([]byte) error, grant func(uint64) error) *streamPipe {
58+ window := brokerwire.StreamWindowBytes
4259 return &streamPipe{
43- maxChunk: maxChunks,
44- maxBytes: int(maxBytes),
45- wake: make(chan struct{}, 1),
46- closedCh: make(chan struct{}),
47- outbound: outbound,
60+ returnAt: streamCreditReturnAt(window),
61+ sendCredit: window,
62+ creditWake: make(chan struct{}),
63+ wake: make(chan struct{}, 1),
64+ closedCh: make(chan struct{}),
65+ ceiling: ceiling,
66+ sendFrame: sendFrame,
67+ grant: grant,
4868 }
4969 }
5070
51-// Read serves the next queued chunk, blocking until one arrives or the pipe
52-// closes. A partial copy retains the unread remainder for the next call.
71+// streamCreditReturnAt is the consumed credit a receiver batches before it
72+// grants it back: half the window, lowered so a sender blocked on a maximum
73+// frame is always released. A sender waits only once more than
74+// window-StreamChunkCredit(max) is unreturned, so granting at or below that
75+// point can never deadlock.
76+func streamCreditReturnAt(window uint64) uint64 {
77+ cost := brokerwire.StreamChunkCredit(int(brokerwire.MaxStreamChunkBytes))
78+ return max(min(window/2, window-cost+1), 1)
79+}
80+
81+// Read serves the next queued frame, blocking until one arrives or the pipe
82+// closes. A partial copy retains the unread remainder for the next call. The
83+// credit of each fully read frame is returned to the peer in batches.
5384 func (p *streamPipe) Read(b []byte) (int, error) {
5485 if len(b) == 0 {
5586 return 0, nil
@@ -57,16 +88,26 @@ func (p *streamPipe) Read(b []byte) (int, error) {
5788 for {
5889 p.mu.Lock()
5990 if len(p.queue) > 0 {
60- chunk := p.queue[0]
61- n := copy(b, chunk)
62- if n == len(chunk) {
63- p.queue[0] = nil
91+ chunk := &p.queue[0]
92+ n := copy(b, chunk.data)
93+ var grant uint64
94+ if n == len(chunk.data) {
95+ p.recvUsed -= chunk.cost
96+ p.recvReturn += chunk.cost
97+ p.queue[0] = pipeChunk{}
6498 p.queue = p.queue[1:]
99+ if p.recvReturn >= p.returnAt && !p.closed {
100+ grant, p.recvReturn = p.recvReturn, 0
101+ }
65102 } else {
66- p.queue[0] = chunk[n:]
103+ chunk.data = chunk.data[n:]
67104 }
68- p.queued -= n
69105 p.mu.Unlock()
106+ if grant > 0 {
107+ // A failed grant means the connection itself is failing; its
108+ // terminal path settles this stream, so there is nothing to retry.
109+ _ = p.grant(grant)
110+ }
70111 return n, nil
71112 }
72113 if p.closed {
@@ -85,40 +126,96 @@ func (p *streamPipe) Read(b []byte) (int, error) {
85126 }
86127 }
87128
88-// Write hands one byte slice to the outbound hook. It returns ErrClosed once the
89-// pipe reached its terminal outcome.
129+// Write sends b in order as frames under the chunk ceiling, waiting for the
130+// peer's credit before each one: a peer that is not reading slows this writer
131+// instead of losing the stream. It returns ErrStreamGone once the pipe reached
132+// its terminal outcome.
90133 func (p *streamPipe) Write(b []byte) (int, error) {
134+ if p.ceiling <= 0 {
135+ return 0, ErrConfig
136+ }
91137 p.mu.Lock()
92138 closed := p.closed
93139 p.mu.Unlock()
94140 if closed {
95141 return 0, ErrStreamGone
96142 }
97- if err := p.outbound(b); err != nil {
98- return 0, err
143+ for data := b; len(data) > 0; {
144+ n := min(len(data), p.ceiling)
145+ if err := p.reserve(n); err != nil {
146+ return 0, err
147+ }
148+ if err := p.sendFrame(data[:n]); err != nil {
149+ return 0, err
150+ }
151+ data = data[n:]
99152 }
100153 return len(b), nil
101154 }
102155
103-// deliver accepts one inbound chunk from the connection reader. It returns
104-// ErrStreamBackpressure when the bound is reached and ErrStreamGone once the
105-// pipe is closed; the caller settles the affected stream in both cases.
156+// reserve spends the send credit of one outbound frame of n bytes, waiting for
157+// the peer to grant more when it is short. It returns ErrStreamGone once the
158+// pipe closes, so a writer waiting for credit is always released.
159+func (p *streamPipe) reserve(n int) error {
160+ cost := brokerwire.StreamChunkCredit(n)
161+ for {
162+ p.mu.Lock()
163+ if p.closed {
164+ p.mu.Unlock()
165+ return ErrStreamGone
166+ }
167+ if p.sendCredit >= cost {
168+ p.sendCredit -= cost
169+ p.mu.Unlock()
170+ return nil
171+ }
172+ wake := p.creditWake
173+ p.mu.Unlock()
174+ select {
175+ case <-wake:
176+ case <-p.closedCh:
177+ }
178+ }
179+}
180+
181+// granted adds credit the peer returned. Credit beyond the window is a peer
182+// flow-control violation.
183+func (p *streamPipe) granted(credit uint64) error {
184+ p.mu.Lock()
185+ defer p.mu.Unlock()
186+ if p.closed {
187+ return nil
188+ }
189+ if credit > brokerwire.StreamWindowBytes-p.sendCredit {
190+ return ErrStreamCredit
191+ }
192+ p.sendCredit += credit
193+ close(p.creditWake)
194+ p.creditWake = make(chan struct{})
195+ return nil
196+}
197+
198+// deliver accepts one inbound frame from the connection reader. It returns
199+// ErrStreamCredit when the peer sent beyond the credit it was granted and
200+// ErrStreamGone once the pipe is closed; the caller settles the affected stream
201+// in both cases. The pipe takes ownership of chunk: callers pass freshly
202+// decoded bytes and never touch them again.
106203 func (p *streamPipe) deliver(chunk []byte) error {
107204 if len(chunk) == 0 {
108205 return nil
109206 }
207+ cost := brokerwire.StreamChunkCredit(len(chunk))
110208 p.mu.Lock()
111209 if p.closed {
112210 p.mu.Unlock()
113211 return ErrStreamGone
114212 }
115- if len(p.queue) >= p.maxChunk || p.queued+len(chunk) > p.maxBytes {
213+ if p.recvUsed+p.recvReturn+cost > brokerwire.StreamWindowBytes {
116214 p.mu.Unlock()
117- return ErrStreamBackpressure
215+ return ErrStreamCredit
118216 }
119- owned := append([]byte(nil), chunk...)
120- p.queue = append(p.queue, owned)
121- p.queued += len(owned)
217+ p.queue = append(p.queue, pipeChunk{data: chunk, cost: cost})
218+ p.recvUsed += cost
122219 p.mu.Unlock()
123220 select {
124221 case p.wake <- struct{}{}:
@@ -128,7 +225,7 @@ func (p *streamPipe) deliver(chunk []byte) error {
128225 }
129226
130227 // closeWith settles the pipe exactly once with cause. A nil cause is an orderly
131-// close; every blocked Read observes it promptly. On a nil cause, any chunks
228+// close; every blocked Read observes it promptly. On a nil cause, any frames
132229 // already queued are kept: Read serves them first and only returns io.EOF once
133230 // the queue is drained, so a reply that arrived just before an orderly peer
134231 // close is never discarded underneath its reader. An error cause is a failure:
@@ -144,7 +241,7 @@ func (p *streamPipe) closeWith(cause error) {
144241 p.err = cause
145242 if cause != nil {
146243 p.queue = nil
147- p.queued = 0
244+ p.recvUsed = 0
148245 }
149246 p.mu.Unlock()
150247 close(p.closedCh)
@@ -3,53 +3,84 @@ package brokeripc
3 import (3 import (
4 "io"4 "io"
5 "sync"5 "sync"
6+
7+ "github.com/bnema/vev/internal/adapters/brokerwire"
6 )8 )
7 9
8-// streamPipe is the private ordered, bounded byte channel between one broker10+// streamPipe is the private ordered, credit-controlled byte channel between one
9-// wire session and the sessionwire framing of one logical stream.11+// broker wire session and the sessionwire framing of one logical stream.
10 //12 //
11-// The wire reader never blocks on a slow consumer: a chunk the peer sent is13+// Each direction of a stream has a brokerwire.StreamWindowBytes credit window.
12-// appended to a bounded queue and refused with ErrStreamBackpressure once the14+// The sender spends brokerwire.StreamChunkCredit per data frame before writing
13-// bound is reached, so only that stream is settled and every sibling and the15+// it and waits while it has too little, so a slow consumer slows its own
14-// physical connection stay untouched. The consumer never blocks the writer:16+// sender instead of failing the stream. The receiver returns the credit of
15-// Read serves already-queued chunks or parks on the wake channel, and Close17+// every frame its consumer has fully read with one StreamWindowUpdate per
16-// releases both ends promptly. Write hands each byte slice to the injected18+// batch. Because a conforming peer never exceeds the window, the wire reader
17-// outbound hook, which owns chunking it under the negotiated stream chunk19+// never blocks: it appends each frame to the queue, and a frame beyond the
18-// ceiling and enqueueing the frames on the connection.20+// granted credit is a peer violation (ErrStreamCredit) that settles only that
21+// stream. Close releases the reader, a writer waiting for credit, and the
22+// writer's peer promptly.
19 type streamPipe struct {23 type streamPipe struct {
20- mu sync.Mutex24+ mu sync.Mutex
21- queue [][]byte25+ queue []pipeChunk
22- queued int26+ closed bool
23- closed bool27+ err error
24- err error28+
25- maxChunk int29+ // recvUsed is the credit held by queued frames, recvReturn the credit of
26- maxBytes int30+ // fully read frames not yet granted back, and returnAt the batch size.
31+ recvUsed uint64
32+ recvReturn uint64
33+ returnAt uint64
34+ // sendCredit is what the peer still allows this side to send.
35+ sendCredit uint64
36+ creditWake chan struct{}
27 37
28 wake chan struct{}38 wake chan struct{}
29 closedCh chan struct{}39 closedCh chan struct{}
30 40
31- outbound func([]byte) error41+ ceiling int
42+ sendFrame func([]byte) error
43+ grant func(uint64) error
32 }44 }
33 45
34-// newStreamPipe builds one bounded stream carriage. outbound must not be nil.46+// pipeChunk is one queued inbound frame and the credit it holds until it is
35-func newStreamPipe(maxChunks int, maxBytes uint64, outbound func([]byte) error) *streamPipe {47+// fully read.
36- if maxChunks <= 0 {48+type pipeChunk struct {
37- maxChunks = DefaultStreamInboundChunks49+ data []byte
38- }50+ cost uint64
39- if maxBytes == 0 {51+}
40- maxBytes = DefaultStreamInboundBytes52+
41- }53+// newStreamPipe builds one stream carriage. Write splits outbound bytes into
54+// frames of at most ceiling bytes, the negotiated stream chunk limit, and hands
55+// each to sendFrame once its credit is reserved; grant returns consumed inbound
56+// credit to the peer. Neither hook may be nil.
57+func newStreamPipe(ceiling int, sendFrame func([]byte) error, grant func(uint64) error) *streamPipe {
58+ window := brokerwire.StreamWindowBytes
42 return &streamPipe{59 return &streamPipe{
43- maxChunk: maxChunks,60+ returnAt: streamCreditReturnAt(window),
44- maxBytes: int(maxBytes),61+ sendCredit: window,
45- wake: make(chan struct{}, 1),62+ creditWake: make(chan struct{}),
46- closedCh: make(chan struct{}),63+ wake: make(chan struct{}, 1),
47- outbound: outbound,64+ closedCh: make(chan struct{}),
65+ ceiling: ceiling,
66+ sendFrame: sendFrame,
67+ grant: grant,
48 }68 }
49 }69 }
50 70
51-// Read serves the next queued chunk, blocking until one arrives or the pipe71+// streamCreditReturnAt is the consumed credit a receiver batches before it
52-// closes. A partial copy retains the unread remainder for the next call.72+// grants it back: half the window, lowered so a sender blocked on a maximum
73+// frame is always released. A sender waits only once more than
74+// window-StreamChunkCredit(max) is unreturned, so granting at or below that
75+// point can never deadlock.
76+func streamCreditReturnAt(window uint64) uint64 {
77+ cost := brokerwire.StreamChunkCredit(int(brokerwire.MaxStreamChunkBytes))
78+ return max(min(window/2, window-cost+1), 1)
79+}
80+
81+// Read serves the next queued frame, blocking until one arrives or the pipe
82+// closes. A partial copy retains the unread remainder for the next call. The
83+// credit of each fully read frame is returned to the peer in batches.
53 func (p *streamPipe) Read(b []byte) (int, error) {84 func (p *streamPipe) Read(b []byte) (int, error) {
54 if len(b) == 0 {85 if len(b) == 0 {
55 return 0, nil86 return 0, nil
@@ -57,16 +88,26 @@ func (p *streamPipe) Read(b []byte) (int, error) {
57 for {88 for {
58 p.mu.Lock()89 p.mu.Lock()
59 if len(p.queue) > 0 {90 if len(p.queue) > 0 {
60- chunk := p.queue[0]91+ chunk := &p.queue[0]
61- n := copy(b, chunk)92+ n := copy(b, chunk.data)
62- if n == len(chunk) {93+ var grant uint64
63- p.queue[0] = nil94+ if n == len(chunk.data) {
95+ p.recvUsed -= chunk.cost
96+ p.recvReturn += chunk.cost
97+ p.queue[0] = pipeChunk{}
64 p.queue = p.queue[1:]98 p.queue = p.queue[1:]
99+ if p.recvReturn >= p.returnAt && !p.closed {
100+ grant, p.recvReturn = p.recvReturn, 0
101+ }
65 } else {102 } else {
66- p.queue[0] = chunk[n:]103+ chunk.data = chunk.data[n:]
67 }104 }
68- p.queued -= n
69 p.mu.Unlock()105 p.mu.Unlock()
106+ if grant > 0 {
107+ // A failed grant means the connection itself is failing; its
108+ // terminal path settles this stream, so there is nothing to retry.
109+ _ = p.grant(grant)
110+ }
70 return n, nil111 return n, nil
71 }112 }
72 if p.closed {113 if p.closed {
@@ -85,40 +126,96 @@ func (p *streamPipe) Read(b []byte) (int, error) {
85 }126 }
86 }127 }
87 128
88-// Write hands one byte slice to the outbound hook. It returns ErrClosed once the129+// Write sends b in order as frames under the chunk ceiling, waiting for the
89-// pipe reached its terminal outcome.130+// peer's credit before each one: a peer that is not reading slows this writer
131+// instead of losing the stream. It returns ErrStreamGone once the pipe reached
132+// its terminal outcome.
90 func (p *streamPipe) Write(b []byte) (int, error) {133 func (p *streamPipe) Write(b []byte) (int, error) {
134+ if p.ceiling <= 0 {
135+ return 0, ErrConfig
136+ }
91 p.mu.Lock()137 p.mu.Lock()
92 closed := p.closed138 closed := p.closed
93 p.mu.Unlock()139 p.mu.Unlock()
94 if closed {140 if closed {
95 return 0, ErrStreamGone141 return 0, ErrStreamGone
96 }142 }
97- if err := p.outbound(b); err != nil {143+ for data := b; len(data) > 0; {
98- return 0, err144+ n := min(len(data), p.ceiling)
145+ if err := p.reserve(n); err != nil {
146+ return 0, err
147+ }
148+ if err := p.sendFrame(data[:n]); err != nil {
149+ return 0, err
150+ }
151+ data = data[n:]
99 }152 }
100 return len(b), nil153 return len(b), nil
101 }154 }
102 155
103-// deliver accepts one inbound chunk from the connection reader. It returns156+// reserve spends the send credit of one outbound frame of n bytes, waiting for
104-// ErrStreamBackpressure when the bound is reached and ErrStreamGone once the157+// the peer to grant more when it is short. It returns ErrStreamGone once the
105-// pipe is closed; the caller settles the affected stream in both cases.158+// pipe closes, so a writer waiting for credit is always released.
159+func (p *streamPipe) reserve(n int) error {
160+ cost := brokerwire.StreamChunkCredit(n)
161+ for {
162+ p.mu.Lock()
163+ if p.closed {
164+ p.mu.Unlock()
165+ return ErrStreamGone
166+ }
167+ if p.sendCredit >= cost {
168+ p.sendCredit -= cost
169+ p.mu.Unlock()
170+ return nil
171+ }
172+ wake := p.creditWake
173+ p.mu.Unlock()
174+ select {
175+ case <-wake:
176+ case <-p.closedCh:
177+ }
178+ }
179+}
180+
181+// granted adds credit the peer returned. Credit beyond the window is a peer
182+// flow-control violation.
183+func (p *streamPipe) granted(credit uint64) error {
184+ p.mu.Lock()
185+ defer p.mu.Unlock()
186+ if p.closed {
187+ return nil
188+ }
189+ if credit > brokerwire.StreamWindowBytes-p.sendCredit {
190+ return ErrStreamCredit
191+ }
192+ p.sendCredit += credit
193+ close(p.creditWake)
194+ p.creditWake = make(chan struct{})
195+ return nil
196+}
197+
198+// deliver accepts one inbound frame from the connection reader. It returns
199+// ErrStreamCredit when the peer sent beyond the credit it was granted and
200+// ErrStreamGone once the pipe is closed; the caller settles the affected stream
201+// in both cases. The pipe takes ownership of chunk: callers pass freshly
202+// decoded bytes and never touch them again.
106 func (p *streamPipe) deliver(chunk []byte) error {203 func (p *streamPipe) deliver(chunk []byte) error {
107 if len(chunk) == 0 {204 if len(chunk) == 0 {
108 return nil205 return nil
109 }206 }
207+ cost := brokerwire.StreamChunkCredit(len(chunk))
110 p.mu.Lock()208 p.mu.Lock()
111 if p.closed {209 if p.closed {
112 p.mu.Unlock()210 p.mu.Unlock()
113 return ErrStreamGone211 return ErrStreamGone
114 }212 }
115- if len(p.queue) >= p.maxChunk || p.queued+len(chunk) > p.maxBytes {213+ if p.recvUsed+p.recvReturn+cost > brokerwire.StreamWindowBytes {
116 p.mu.Unlock()214 p.mu.Unlock()
117- return ErrStreamBackpressure215+ return ErrStreamCredit
118 }216 }
119- owned := append([]byte(nil), chunk...)217+ p.queue = append(p.queue, pipeChunk{data: chunk, cost: cost})
120- p.queue = append(p.queue, owned)218+ p.recvUsed += cost
121- p.queued += len(owned)
122 p.mu.Unlock()219 p.mu.Unlock()
123 select {220 select {
124 case p.wake <- struct{}{}:221 case p.wake <- struct{}{}:
@@ -128,7 +225,7 @@ func (p *streamPipe) deliver(chunk []byte) error {
128 }225 }
129 226
130 // closeWith settles the pipe exactly once with cause. A nil cause is an orderly227 // closeWith settles the pipe exactly once with cause. A nil cause is an orderly
131-// close; every blocked Read observes it promptly. On a nil cause, any chunks228+// close; every blocked Read observes it promptly. On a nil cause, any frames
132 // already queued are kept: Read serves them first and only returns io.EOF once229 // already queued are kept: Read serves them first and only returns io.EOF once
133 // the queue is drained, so a reply that arrived just before an orderly peer230 // the queue is drained, so a reply that arrived just before an orderly peer
134 // close is never discarded underneath its reader. An error cause is a failure:231 // close is never discarded underneath its reader. An error cause is a failure:
@@ -144,7 +241,7 @@ func (p *streamPipe) closeWith(cause error) {
144 p.err = cause241 p.err = cause
145 if cause != nil {242 if cause != nil {
146 p.queue = nil243 p.queue = nil
147- p.queued = 0244+ p.recvUsed = 0
148 }245 }
149 p.mu.Unlock()246 p.mu.Unlock()
150 close(p.closedCh)247 close(p.closedCh)
M internal/adapters/brokeripc/pipe_test.go +100 -19
@@ -5,6 +5,7 @@ import (
55 "errors"
66 "io"
77 "testing"
8+ "time"
89
910 "github.com/bnema/vev/internal/adapters/brokerwire"
1011 "github.com/bnema/vev/internal/protocol"
@@ -71,7 +72,7 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) {
7172
7273 for _, tt := range tests {
7374 t.Run(tt.name, func(t *testing.T) {
74- p := newStreamPipe(0, 0, func([]byte) error { return nil })
75+ p := newTestPipe()
7576 for _, chunk := range tt.chunks {
7677 require.NoError(t, p.deliver(chunk))
7778 }
@@ -109,26 +110,106 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) {
109110 }
110111 }
111112
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, and overflow fails the whole stream
116-// (ErrStreamBackpressure). The frame size is a typical text repaint: a
117-// near-full page of a 500x140 terminal measured about 70 KB. Uncompressed diff
118-// frames with per-cell truecolor SGR can be far larger (estimated near 1 MB at
119-// 300x80), and eight of those can still overflow the default 4 MiB bound.
120-func TestStreamPipeHoldsAFullOutputWindow(t *testing.T) {
121- const frameBytes = 72 << 10
113+// newTestPipe is a pipe whose outbound writes and credit grants go nowhere.
114+func newTestPipe() *streamPipe {
115+ return newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return nil }, func(uint64) error { return nil })
116+}
117+
118+// TestStreamWindowCoversTheSessionOutputWindow pins the sizing relation
119+// between the two flow-control layers: one broker stream window holds the
120+// daemon's unacknowledged byte budget, so the daemon is mostly paced by its own
121+// ACK window and coalesces frames. Credit may still pace it at the margin: the
122+// receiver batches up to half a window before returning it, chunks cost 64
123+// bytes of overhead, and one frame may overrun the byte budget (a big image).
124+// Credit stays the hard bound either way.
125+func TestStreamWindowCoversTheSessionOutputWindow(t *testing.T) {
126+ require.GreaterOrEqual(t, brokerwire.StreamWindowBytes, uint64(protocol.MaxOutputWindowBytes),
127+ "one stream window must hold the daemon's whole unacknowledged byte budget")
128+}
129+
130+// TestStreamPipeCredit covers the receive and send sides of stream credit:
131+// a peer may send up to one window, the consumer's reads return credit in
132+// batches, a sender waits instead of failing while it has none, and grants or
133+// data beyond the window are flow-control violations.
134+func TestStreamPipeCredit(t *testing.T) {
122135 chunk := int(brokerwire.MaxStreamChunkBytes)
123- p := newStreamPipe(0, 0, func([]byte) error { return nil })
124- payload := bytes.Repeat([]byte{'x'}, frameBytes)
125- for frame := range protocol.MaxOutputWindow {
126- for data := payload; len(data) > 0; {
127- n := min(len(data), chunk)
128- require.NoError(t, p.deliver(data[:n]), "frame %d overflowed the client pipe", frame)
129- data = data[n:]
136+ cost := brokerwire.StreamChunkCredit(chunk)
137+ perWindow := int(brokerwire.StreamWindowBytes / cost)
138+
139+ t.Run("a full window is accepted and one more frame is a violation", func(t *testing.T) {
140+ p := newTestPipe()
141+ for range perWindow {
142+ require.NoError(t, p.deliver(make([]byte, chunk)))
130143 }
131- }
144+ require.ErrorIs(t, p.deliver(make([]byte, chunk)), ErrStreamCredit)
145+ })
146+
147+ t.Run("reads return credit in batches", func(t *testing.T) {
148+ var grants []uint64
149+ p := newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return nil }, func(c uint64) error { grants = append(grants, c); return nil })
150+ for range perWindow {
151+ require.NoError(t, p.deliver(make([]byte, chunk)))
152+ }
153+ buf := make([]byte, chunk)
154+ for range perWindow {
155+ _, err := io.ReadFull(p, buf)
156+ require.NoError(t, err)
157+ }
158+ require.NotEmpty(t, grants)
159+ total := uint64(0)
160+ for _, g := range grants {
161+ require.GreaterOrEqual(t, g, streamCreditReturnAt(brokerwire.StreamWindowBytes))
162+ total += g
163+ }
164+ require.LessOrEqual(t, total, uint64(perWindow)*cost)
165+ // Once the returned credit reaches the peer, the window is open again.
166+ require.NoError(t, p.deliver(make([]byte, chunk)))
167+ })
168+
169+ t.Run("a sender waits for credit and resumes on a grant", func(t *testing.T) {
170+ p := newTestPipe()
171+ for range perWindow {
172+ require.NoError(t, p.reserve(chunk))
173+ }
174+ reserved := make(chan error, 1)
175+ go func() { reserved <- p.reserve(chunk) }()
176+ select {
177+ case err := <-reserved:
178+ t.Fatalf("reserve returned %v without credit", err)
179+ case <-time.After(20 * time.Millisecond):
180+ }
181+ require.NoError(t, p.granted(cost))
182+ select {
183+ case err := <-reserved:
184+ require.NoError(t, err)
185+ case <-time.After(5 * time.Second):
186+ t.Fatal("a grant must release a sender waiting for credit")
187+ }
188+ })
189+
190+ t.Run("close releases a sender waiting for credit", func(t *testing.T) {
191+ p := newTestPipe()
192+ for range perWindow {
193+ require.NoError(t, p.reserve(chunk))
194+ }
195+ reserved := make(chan error, 1)
196+ go func() { reserved <- p.reserve(chunk) }()
197+ p.closeWith(ErrStreamGone)
198+ select {
199+ case err := <-reserved:
200+ require.ErrorIs(t, err, ErrStreamGone)
201+ case <-time.After(5 * time.Second):
202+ t.Fatal("close must release a sender waiting for credit")
203+ }
204+ })
205+
206+ t.Run("a grant beyond the window is a violation", func(t *testing.T) {
207+ p := newTestPipe()
208+ require.ErrorIs(t, p.granted(1), ErrStreamCredit)
209+ require.NoError(t, p.reserve(chunk))
210+ require.NoError(t, p.granted(cost))
211+ require.ErrorIs(t, p.granted(1), ErrStreamCredit)
212+ })
132213 }
133214
134215 // TestClientStreamPrefersTheFullOutputWindow pins what a real broker-routed
@@ -5,6 +5,7 @@ import (
5 "errors"5 "errors"
6 "io"6 "io"
7 "testing"7 "testing"
8+ "time"
8 9
9 "github.com/bnema/vev/internal/adapters/brokerwire"10 "github.com/bnema/vev/internal/adapters/brokerwire"
10 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
@@ -71,7 +72,7 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) {
71 72
72 for _, tt := range tests {73 for _, tt := range tests {
73 t.Run(tt.name, func(t *testing.T) {74 t.Run(tt.name, func(t *testing.T) {
74- p := newStreamPipe(0, 0, func([]byte) error { return nil })75+ p := newTestPipe()
75 for _, chunk := range tt.chunks {76 for _, chunk := range tt.chunks {
76 require.NoError(t, p.deliver(chunk))77 require.NoError(t, p.deliver(chunk))
77 }78 }
@@ -109,26 +110,106 @@ func TestStreamPipeCloseWithPreservesQueuedDataOnOrderlyClose(t *testing.T) {
109 }110 }
110 }111 }
111 112
112-// TestStreamPipeHoldsAFullOutputWindow pins the client pipe headroom for the113+// newTestPipe is a pipe whose outbound writes and credit grants go nowhere.
113-// session output window. A client claims protocol.MaxOutputWindow unacked114+func newTestPipe() *streamPipe {
114-// Output frames; with a terminal that is not draining, all of them may sit in115+ return newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return nil }, func(uint64) error { return nil })
115-// this pipe at once, and overflow fails the whole stream116+}
116-// (ErrStreamBackpressure). The frame size is a typical text repaint: a117+
117-// near-full page of a 500x140 terminal measured about 70 KB. Uncompressed diff118+// TestStreamWindowCoversTheSessionOutputWindow pins the sizing relation
118-// frames with per-cell truecolor SGR can be far larger (estimated near 1 MB at119+// between the two flow-control layers: one broker stream window holds the
119-// 300x80), and eight of those can still overflow the default 4 MiB bound.120+// daemon's unacknowledged byte budget, so the daemon is mostly paced by its own
120-func TestStreamPipeHoldsAFullOutputWindow(t *testing.T) {121+// ACK window and coalesces frames. Credit may still pace it at the margin: the
121- const frameBytes = 72 << 10122+// receiver batches up to half a window before returning it, chunks cost 64
123+// bytes of overhead, and one frame may overrun the byte budget (a big image).
124+// Credit stays the hard bound either way.
125+func TestStreamWindowCoversTheSessionOutputWindow(t *testing.T) {
126+ require.GreaterOrEqual(t, brokerwire.StreamWindowBytes, uint64(protocol.MaxOutputWindowBytes),
127+ "one stream window must hold the daemon's whole unacknowledged byte budget")
128+}
129+
130+// TestStreamPipeCredit covers the receive and send sides of stream credit:
131+// a peer may send up to one window, the consumer's reads return credit in
132+// batches, a sender waits instead of failing while it has none, and grants or
133+// data beyond the window are flow-control violations.
134+func TestStreamPipeCredit(t *testing.T) {
122 chunk := int(brokerwire.MaxStreamChunkBytes)135 chunk := int(brokerwire.MaxStreamChunkBytes)
123- p := newStreamPipe(0, 0, func([]byte) error { return nil })136+ cost := brokerwire.StreamChunkCredit(chunk)
124- payload := bytes.Repeat([]byte{'x'}, frameBytes)137+ perWindow := int(brokerwire.StreamWindowBytes / cost)
125- for frame := range protocol.MaxOutputWindow {138+
126- for data := payload; len(data) > 0; {139+ t.Run("a full window is accepted and one more frame is a violation", func(t *testing.T) {
127- n := min(len(data), chunk)140+ p := newTestPipe()
128- require.NoError(t, p.deliver(data[:n]), "frame %d overflowed the client pipe", frame)141+ for range perWindow {
129- data = data[n:]142+ require.NoError(t, p.deliver(make([]byte, chunk)))
130 }143 }
131- }144+ require.ErrorIs(t, p.deliver(make([]byte, chunk)), ErrStreamCredit)
145+ })
146+
147+ t.Run("reads return credit in batches", func(t *testing.T) {
148+ var grants []uint64
149+ p := newStreamPipe(int(brokerwire.MaxStreamChunkBytes), func([]byte) error { return nil }, func(c uint64) error { grants = append(grants, c); return nil })
150+ for range perWindow {
151+ require.NoError(t, p.deliver(make([]byte, chunk)))
152+ }
153+ buf := make([]byte, chunk)
154+ for range perWindow {
155+ _, err := io.ReadFull(p, buf)
156+ require.NoError(t, err)
157+ }
158+ require.NotEmpty(t, grants)
159+ total := uint64(0)
160+ for _, g := range grants {
161+ require.GreaterOrEqual(t, g, streamCreditReturnAt(brokerwire.StreamWindowBytes))
162+ total += g
163+ }
164+ require.LessOrEqual(t, total, uint64(perWindow)*cost)
165+ // Once the returned credit reaches the peer, the window is open again.
166+ require.NoError(t, p.deliver(make([]byte, chunk)))
167+ })
168+
169+ t.Run("a sender waits for credit and resumes on a grant", func(t *testing.T) {
170+ p := newTestPipe()
171+ for range perWindow {
172+ require.NoError(t, p.reserve(chunk))
173+ }
174+ reserved := make(chan error, 1)
175+ go func() { reserved <- p.reserve(chunk) }()
176+ select {
177+ case err := <-reserved:
178+ t.Fatalf("reserve returned %v without credit", err)
179+ case <-time.After(20 * time.Millisecond):
180+ }
181+ require.NoError(t, p.granted(cost))
182+ select {
183+ case err := <-reserved:
184+ require.NoError(t, err)
185+ case <-time.After(5 * time.Second):
186+ t.Fatal("a grant must release a sender waiting for credit")
187+ }
188+ })
189+
190+ t.Run("close releases a sender waiting for credit", func(t *testing.T) {
191+ p := newTestPipe()
192+ for range perWindow {
193+ require.NoError(t, p.reserve(chunk))
194+ }
195+ reserved := make(chan error, 1)
196+ go func() { reserved <- p.reserve(chunk) }()
197+ p.closeWith(ErrStreamGone)
198+ select {
199+ case err := <-reserved:
200+ require.ErrorIs(t, err, ErrStreamGone)
201+ case <-time.After(5 * time.Second):
202+ t.Fatal("close must release a sender waiting for credit")
203+ }
204+ })
205+
206+ t.Run("a grant beyond the window is a violation", func(t *testing.T) {
207+ p := newTestPipe()
208+ require.ErrorIs(t, p.granted(1), ErrStreamCredit)
209+ require.NoError(t, p.reserve(chunk))
210+ require.NoError(t, p.granted(cost))
211+ require.ErrorIs(t, p.granted(1), ErrStreamCredit)
212+ })
132 }213 }
133 214
134 // TestClientStreamPrefersTheFullOutputWindow pins what a real broker-routed215 // TestClientStreamPrefersTheFullOutputWindow pins what a real broker-routed
M internal/adapters/brokeripc/server.go +5 -0
@@ -763,6 +763,11 @@ func (s *serverSession) dispatch(message brokerwire.ClientMessage) error {
763763 }
764764 s.closeStreamByPeer(m.Stream)
765765 return nil
766+ case brokerwire.StreamWindowUpdate:
767+ if !s.scopeMatches(m.Epoch, m.Connection) {
768+ return s.refuseScope()
769+ }
770+ return s.grantStreamCredit(m)
766771 case brokerwire.StartPreview:
767772 if !s.scopeMatches(m.Epoch, m.Connection) {
768773 return s.refuseScope()
@@ -763,6 +763,11 @@ func (s *serverSession) dispatch(message brokerwire.ClientMessage) error {
763 }763 }
764 s.closeStreamByPeer(m.Stream)764 s.closeStreamByPeer(m.Stream)
765 return nil765 return nil
766+ case brokerwire.StreamWindowUpdate:
767+ if !s.scopeMatches(m.Epoch, m.Connection) {
768+ return s.refuseScope()
769+ }
770+ return s.grantStreamCredit(m)
766 case brokerwire.StartPreview:771 case brokerwire.StartPreview:
767 if !s.scopeMatches(m.Epoch, m.Connection) {772 if !s.scopeMatches(m.Epoch, m.Connection) {
768 return s.refuseScope()773 return s.refuseScope()
M internal/adapters/brokeripc/session_test.go +56 -22
@@ -1,6 +1,7 @@
11 package brokeripc
22
33 import (
4+ "bytes"
45 "context"
56 "errors"
67 "io"
@@ -856,43 +857,76 @@ func TestStalledSubscriberDoesNotBlockOtherClients(t *testing.T) {
856857 requireConnectionDelegates(t, active)
857858 }
858859
859-// TestClientStreamBackpressureSettlesOnlyThatStream proves a local consumer that
860-// stops draining a stream's bounded inbound queue settles exactly that stream:
861-// the peer is told, the stream reports backpressure, and the connection and its
862-// operations stay up.
863-func TestClientStreamBackpressureSettlesOnlyThatStream(t *testing.T) {
860+// TestStalledClientStreamWaitsForCredit proves a local consumer that stops
861+// reading slows the broker relay instead of losing its stream: the daemon's
862+// messages beyond one stream window wait at the broker, every one is delivered
863+// in order once the consumer reads again, and the connection stays usable
864+// meanwhile.
865+func TestStalledClientStreamWaitsForCredit(t *testing.T) {
864866 e := startEndpoint(t, Config{MaxClients: 2})
865- client := e.dialWith(Config{StreamInboundChunks: 1})
867+ client := e.dial()
866868 _ = e.accept()
867869 core := e.authority.last()
868870
869- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
871+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
870872 defer cancel()
871873 stream, err := client.OpenStream(ctx, openRequest(nextStreamID(t, client)))
872874 require.NoError(t, err)
875+ t.Cleanup(func() { _ = stream.Close() })
873876 conn := core.logicalConn(1)
874877 require.NotNil(t, conn)
878+ // Complete the session preamble before the daemon speaks.
879+ require.NoError(t, stream.SendClient(protocol.Ping{}))
880+ select {
881+ case <-conn.fromClient:
882+ case <-ctx.Done():
883+ t.Fatal("client message never reached the broker core")
884+ }
875885
876- // Two daemon messages arrive while the local consumer never reads: the
877- // bounded capacity-one queue refuses the second one.
878- conn.toClient <- protocol.Pong{}
879- conn.toClient <- protocol.Pong{}
886+ // Well over one stream window of daemon output while nothing reads. Side
887+ // effect Outputs need no view context and are never compressed.
888+ const messages = 24
889+ data := bytes.Repeat([]byte{'x'}, 512<<10)
890+ go func() {
891+ for i := range messages {
892+ payload := append([]byte{byte(i)}, data...)
893+ select {
894+ case conn.toClient <- protocol.Output{Epoch: 1, Size: domain.Size{Cols: 80, Rows: 24}, Data: payload}:
895+ case <-ctx.Done():
896+ return
897+ }
898+ }
899+ }()
880900
901+ // The relay fills exactly one window of the client pipe and then waits on
902+ // credit instead of failing: the stream stays open and the connection keeps
903+ // serving other work.
904+ local, ok := stream.(*clientStream)
905+ require.True(t, ok)
906+ full := brokerwire.StreamWindowBytes - brokerwire.StreamChunkCredit(int(brokerwire.MaxStreamChunkBytes))
907+ held := func() uint64 {
908+ local.pipe.mu.Lock()
909+ defer local.pipe.mu.Unlock()
910+ return local.pipe.recvUsed + local.pipe.recvReturn
911+ }
912+ require.Eventually(t, func() bool { return held() > full }, 5*time.Second, time.Millisecond,
913+ "the relay must fill one stream window")
914+ // Data beyond the credit would settle the stream with ErrStreamCredit.
881915 select {
882916 case <-stream.Done():
883- case <-time.After(5 * time.Second):
884- t.Fatal("a stalled stream consumer must settle the stream")
917+ t.Fatalf("a stalled consumer must not settle its stream: %v", stream.Err())
918+ default:
885919 }
886- require.ErrorIs(t, stream.(*clientStream).Err(), ErrStreamBackpressure)
887-
888- require.Eventually(t, func() bool {
889- core.mu.Lock()
890- defer core.mu.Unlock()
891- return len(core.closedIDs) > 0
892- }, 5*time.Second, 10*time.Millisecond, "the broker must be told to retire the stalled stream")
893-
894- // The connection survives a stream-local stall.
895920 requireConnectionDelegates(t, client)
921+
922+ for i := range messages {
923+ message, err := stream.ReceiveServer()
924+ require.NoError(t, err)
925+ got, ok := message.(protocol.Output)
926+ require.True(t, ok, "message %d is %T", i, message)
927+ require.Len(t, got.Data, len(data)+1)
928+ require.Equal(t, byte(i), got.Data[0], "messages arrive in order")
929+ }
896930 }
897931
898932 // TestOrderlyStreamCloseReportsNoError proves a local close is an orderly end:
@@ -1,6 +1,7 @@
1 package brokeripc1 package brokeripc
2 2
3 import (3 import (
4+ "bytes"
4 "context"5 "context"
5 "errors"6 "errors"
6 "io"7 "io"
@@ -856,43 +857,76 @@ func TestStalledSubscriberDoesNotBlockOtherClients(t *testing.T) {
856 requireConnectionDelegates(t, active)857 requireConnectionDelegates(t, active)
857 }858 }
858 859
859-// TestClientStreamBackpressureSettlesOnlyThatStream proves a local consumer that860+// TestStalledClientStreamWaitsForCredit proves a local consumer that stops
860-// stops draining a stream's bounded inbound queue settles exactly that stream:861+// reading slows the broker relay instead of losing its stream: the daemon's
861-// the peer is told, the stream reports backpressure, and the connection and its862+// messages beyond one stream window wait at the broker, every one is delivered
862-// operations stay up.863+// in order once the consumer reads again, and the connection stays usable
863-func TestClientStreamBackpressureSettlesOnlyThatStream(t *testing.T) {864+// meanwhile.
865+func TestStalledClientStreamWaitsForCredit(t *testing.T) {
864 e := startEndpoint(t, Config{MaxClients: 2})866 e := startEndpoint(t, Config{MaxClients: 2})
865- client := e.dialWith(Config{StreamInboundChunks: 1})867+ client := e.dial()
866 _ = e.accept()868 _ = e.accept()
867 core := e.authority.last()869 core := e.authority.last()
868 870
869- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)871+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
870 defer cancel()872 defer cancel()
871 stream, err := client.OpenStream(ctx, openRequest(nextStreamID(t, client)))873 stream, err := client.OpenStream(ctx, openRequest(nextStreamID(t, client)))
872 require.NoError(t, err)874 require.NoError(t, err)
875+ t.Cleanup(func() { _ = stream.Close() })
873 conn := core.logicalConn(1)876 conn := core.logicalConn(1)
874 require.NotNil(t, conn)877 require.NotNil(t, conn)
878+ // Complete the session preamble before the daemon speaks.
879+ require.NoError(t, stream.SendClient(protocol.Ping{}))
880+ select {
881+ case <-conn.fromClient:
882+ case <-ctx.Done():
883+ t.Fatal("client message never reached the broker core")
884+ }
875 885
876- // Two daemon messages arrive while the local consumer never reads: the886+ // Well over one stream window of daemon output while nothing reads. Side
877- // bounded capacity-one queue refuses the second one.887+ // effect Outputs need no view context and are never compressed.
878- conn.toClient <- protocol.Pong{}888+ const messages = 24
879- conn.toClient <- protocol.Pong{}889+ data := bytes.Repeat([]byte{'x'}, 512<<10)
890+ go func() {
891+ for i := range messages {
892+ payload := append([]byte{byte(i)}, data...)
893+ select {
894+ case conn.toClient <- protocol.Output{Epoch: 1, Size: domain.Size{Cols: 80, Rows: 24}, Data: payload}:
895+ case <-ctx.Done():
896+ return
897+ }
898+ }
899+ }()
880 900
901+ // The relay fills exactly one window of the client pipe and then waits on
902+ // credit instead of failing: the stream stays open and the connection keeps
903+ // serving other work.
904+ local, ok := stream.(*clientStream)
905+ require.True(t, ok)
906+ full := brokerwire.StreamWindowBytes - brokerwire.StreamChunkCredit(int(brokerwire.MaxStreamChunkBytes))
907+ held := func() uint64 {
908+ local.pipe.mu.Lock()
909+ defer local.pipe.mu.Unlock()
910+ return local.pipe.recvUsed + local.pipe.recvReturn
911+ }
912+ require.Eventually(t, func() bool { return held() > full }, 5*time.Second, time.Millisecond,
913+ "the relay must fill one stream window")
914+ // Data beyond the credit would settle the stream with ErrStreamCredit.
881 select {915 select {
882 case <-stream.Done():916 case <-stream.Done():
883- case <-time.After(5 * time.Second):917+ t.Fatalf("a stalled consumer must not settle its stream: %v", stream.Err())
884- t.Fatal("a stalled stream consumer must settle the stream")918+ default:
885 }919 }
886- require.ErrorIs(t, stream.(*clientStream).Err(), ErrStreamBackpressure)
887-
888- require.Eventually(t, func() bool {
889- core.mu.Lock()
890- defer core.mu.Unlock()
891- return len(core.closedIDs) > 0
892- }, 5*time.Second, 10*time.Millisecond, "the broker must be told to retire the stalled stream")
893-
894- // The connection survives a stream-local stall.
895 requireConnectionDelegates(t, client)920 requireConnectionDelegates(t, client)
921+
922+ for i := range messages {
923+ message, err := stream.ReceiveServer()
924+ require.NoError(t, err)
925+ got, ok := message.(protocol.Output)
926+ require.True(t, ok, "message %d is %T", i, message)
927+ require.Len(t, got.Data, len(data)+1)
928+ require.Equal(t, byte(i), got.Data[0], "messages arrive in order")
929+ }
896 }930 }
897 931
898 // TestOrderlyStreamCloseReportsNoError proves a local close is an orderly end:932 // TestOrderlyStreamCloseReportsNoError proves a local close is an orderly end:
M internal/adapters/brokeripc/stream.go +67 -62
@@ -42,28 +42,25 @@ func newServerStream(s *serverSession, id ports.BrokerStreamID, core ports.Broke
4242 return nil, ErrConfig
4343 }
4444 st := &serverStream{session: s, id: id, core: core, done: make(chan struct{})}
45- st.pipe = newStreamPipe(s.cfg.StreamInboundChunks, s.cfg.StreamInboundBytes, st.sendChunk)
45+ st.pipe = newStreamPipe(int(s.ceilings.StreamChunkLimit), st.sendFrame, st.grantCredit)
4646 st.carriage = newCarriage(st.pipe)
4747 return st, nil
4848 }
4949
50-// sendChunk splits one outbound byte slice under the negotiated stream chunk
51-// ceiling and writes each frame in order.
52-func (st *serverStream) sendChunk(data []byte) error {
53- ceiling := int(st.session.ceilings.StreamChunkLimit)
54- if ceiling <= 0 {
55- return ErrConfig
56- }
57- for len(data) > 0 {
58- n := min(len(data), ceiling)
59- if err := st.session.send(brokerwire.ServerStreamData{
60- Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Data: data[:n],
61- }); err != nil {
62- return err
63- }
64- data = data[n:]
65- }
66- return nil
50+// sendFrame writes one credited outbound frame to the client. A client that
51+// is not reading slows this relay, and through the daemon link the daemon,
52+// instead of losing its stream.
53+func (st *serverStream) sendFrame(data []byte) error {
54+ return st.session.send(brokerwire.ServerStreamData{
55+ Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Data: data,
56+ })
57+}
58+
59+// grantCredit returns consumed inbound credit to the client.
60+func (st *serverStream) grantCredit(credit uint64) error {
61+ return st.session.send(brokerwire.StreamWindowUpdate{
62+ Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Credit: credit,
63+ })
6764 }
6865
6966 // deliver accepts one inbound chunk from the connection reader.
@@ -347,40 +344,51 @@ func (s *serverSession) closeStreamByPeer(id ports.BrokerStreamID) {
347344 _ = s.core.CloseStream(s.scope.Connection, id)
348345 }
349346
350-// deliverStreamData routes one inbound client chunk to its bridge. Backpressure
351-// settles only that stream; a chunk for an identity the peer never opened is a
352-// protocol violation.
347+// deliverStreamData routes one inbound client chunk to its bridge. Data beyond
348+// the granted credit settles only that stream; a chunk for an identity the peer
349+// never opened is a protocol violation.
353350 func (s *serverSession) deliverStreamData(m brokerwire.ClientStreamData) error {
354- disposition, err := s.conn.StreamData(m.Stream)
355- if err != nil {
356- // Stream data for a stream that was never allocated, or before it was
357- // confirmed open, is a peer protocol violation.
358- return errors.Join(ErrProtocol, err)
351+ st, err := s.streamFor(m.Stream)
352+ if st == nil || err != nil {
353+ return err
359354 }
360- if disposition == brokerwire.StreamDiscarded {
361- return nil
355+ if err := st.deliver(m.Data); errors.Is(err, ErrStreamCredit) {
356+ s.settleStream(st, ErrStreamCredit)
362357 }
363- st := s.lookupStream(m.Stream)
364- if st == nil {
365- // The bridge was settled between the tracker check and the lookup (a
366- // stream-local failure won the race); the chunk is discarded with it.
367- return nil
358+ // Any other error means the stream already reached its terminal outcome;
359+ // its data is discarded.
360+ return nil
361+}
362+
363+// grantStreamCredit applies one client credit grant to its bridge.
364+func (s *serverSession) grantStreamCredit(m brokerwire.StreamWindowUpdate) error {
365+ st, err := s.streamFor(m.Stream)
366+ if st == nil || err != nil {
367+ return err
368368 }
369- switch err := st.deliver(m.Data); {
370- case err == nil:
371- return nil
372- case errors.Is(err, ErrStreamBackpressure):
373- s.settleStream(st, ErrStreamBackpressure)
374- return nil
375- default:
376- // The stream already reached its terminal outcome; its data is
377- // discarded.
378- return nil
369+ if err := st.pipe.granted(m.Credit); err != nil {
370+ s.settleStream(st, err)
379371 }
372+ return nil
373+}
374+
375+// streamFor classifies one stream-scoped frame and returns its live bridge. A
376+// frame for a retired stream, or one settled between the tracker check and the
377+// lookup, yields no bridge and no error; a frame for a stream that was never
378+// allocated, or before it was confirmed open, is a peer protocol violation.
379+func (s *serverSession) streamFor(id ports.BrokerStreamID) (*serverStream, error) {
380+ disposition, err := s.conn.StreamData(id)
381+ if err != nil {
382+ return nil, errors.Join(ErrProtocol, err)
383+ }
384+ if disposition == brokerwire.StreamDiscarded {
385+ return nil, nil
386+ }
387+ return s.lookupStream(id), nil
380388 }
381389
382390 // clientStream is the client-side half of one logical stream: the sessionwire
383-// client connection over the stream's bounded pipe, plus the terminal outcome a
391+// client connection over the stream's credit-controlled pipe, plus the terminal outcome a
384392 // ports.BrokerLogicalConnection exposes independently of reads.
385393 type clientStream struct {
386394 ports.ClientConnection
@@ -407,7 +415,7 @@ func newClientStream(c *client, id ports.BrokerStreamID) (*clientStream, error)
407415 opened: make(chan error, 1),
408416 done: make(chan struct{}),
409417 }
410- st.pipe = newStreamPipe(c.cfg.StreamInboundChunks, c.cfg.StreamInboundBytes, st.sendChunk)
418+ st.pipe = newStreamPipe(int(c.ceilings.StreamChunkLimit), st.sendFrame, st.grantCredit)
411419 st.carriage = newCarriage(st.pipe)
412420 st.ClientConnection = sessionwire.NewClientConnection(st.carriage)
413421 if st.ClientConnection == nil {
@@ -416,23 +424,20 @@ func newClientStream(c *client, id ports.BrokerStreamID) (*clientStream, error)
416424 return st, nil
417425 }
418426
419-// sendChunk splits one outbound client byte slice under the negotiated stream
420-// chunk ceiling.
421-func (st *clientStream) sendChunk(data []byte) error {
422- ceiling := int(st.client.ceilings.StreamChunkLimit)
423- if ceiling <= 0 {
424- return ErrConfig
425- }
426- for len(data) > 0 {
427- n := min(len(data), ceiling)
428- if err := st.client.send(brokerwire.ClientStreamData{
429- Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Data: data[:n],
430- }); err != nil {
431- return err
432- }
433- data = data[n:]
434- }
435- return nil
427+// sendFrame writes one credited outbound frame to the broker.
428+func (st *clientStream) sendFrame(data []byte) error {
429+ return st.client.send(brokerwire.ClientStreamData{
430+ Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Data: data,
431+ })
432+}
433+
434+// grantCredit returns consumed inbound credit to the broker. It is written
435+// synchronously because a dropped grant would stall the stream forever; the
436+// write never waits long, since the broker's reader never blocks on a stream.
437+func (st *clientStream) grantCredit(credit uint64) error {
438+ return st.client.send(brokerwire.StreamWindowUpdate{
439+ Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Credit: credit,
440+ })
436441 }
437442
438443 // deliver accepts one inbound server chunk.
@@ -42,28 +42,25 @@ func newServerStream(s *serverSession, id ports.BrokerStreamID, core ports.Broke
42 return nil, ErrConfig42 return nil, ErrConfig
43 }43 }
44 st := &serverStream{session: s, id: id, core: core, done: make(chan struct{})}44 st := &serverStream{session: s, id: id, core: core, done: make(chan struct{})}
45- st.pipe = newStreamPipe(s.cfg.StreamInboundChunks, s.cfg.StreamInboundBytes, st.sendChunk)45+ st.pipe = newStreamPipe(int(s.ceilings.StreamChunkLimit), st.sendFrame, st.grantCredit)
46 st.carriage = newCarriage(st.pipe)46 st.carriage = newCarriage(st.pipe)
47 return st, nil47 return st, nil
48 }48 }
49 49
50-// sendChunk splits one outbound byte slice under the negotiated stream chunk50+// sendFrame writes one credited outbound frame to the client. A client that
51-// ceiling and writes each frame in order.51+// is not reading slows this relay, and through the daemon link the daemon,
52-func (st *serverStream) sendChunk(data []byte) error {52+// instead of losing its stream.
53- ceiling := int(st.session.ceilings.StreamChunkLimit)53+func (st *serverStream) sendFrame(data []byte) error {
54- if ceiling <= 0 {54+ return st.session.send(brokerwire.ServerStreamData{
55- return ErrConfig55+ Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Data: data,
56- }56+ })
57- for len(data) > 0 {57+}
58- n := min(len(data), ceiling)58+
59- if err := st.session.send(brokerwire.ServerStreamData{59+// grantCredit returns consumed inbound credit to the client.
60- Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Data: data[:n],60+func (st *serverStream) grantCredit(credit uint64) error {
61- }); err != nil {61+ return st.session.send(brokerwire.StreamWindowUpdate{
62- return err62+ Epoch: st.session.epoch, Connection: st.session.scope.Connection, Stream: st.id, Credit: credit,
63- }63+ })
64- data = data[n:]
65- }
66- return nil
67 }64 }
68 65
69 // deliver accepts one inbound chunk from the connection reader.66 // deliver accepts one inbound chunk from the connection reader.
@@ -347,40 +344,51 @@ func (s *serverSession) closeStreamByPeer(id ports.BrokerStreamID) {
347 _ = s.core.CloseStream(s.scope.Connection, id)344 _ = s.core.CloseStream(s.scope.Connection, id)
348 }345 }
349 346
350-// deliverStreamData routes one inbound client chunk to its bridge. Backpressure347+// deliverStreamData routes one inbound client chunk to its bridge. Data beyond
351-// settles only that stream; a chunk for an identity the peer never opened is a348+// the granted credit settles only that stream; a chunk for an identity the peer
352-// protocol violation.349+// never opened is a protocol violation.
353 func (s *serverSession) deliverStreamData(m brokerwire.ClientStreamData) error {350 func (s *serverSession) deliverStreamData(m brokerwire.ClientStreamData) error {
354- disposition, err := s.conn.StreamData(m.Stream)351+ st, err := s.streamFor(m.Stream)
355- if err != nil {352+ if st == nil || err != nil {
356- // Stream data for a stream that was never allocated, or before it was353+ return err
357- // confirmed open, is a peer protocol violation.
358- return errors.Join(ErrProtocol, err)
359 }354 }
360- if disposition == brokerwire.StreamDiscarded {355+ if err := st.deliver(m.Data); errors.Is(err, ErrStreamCredit) {
361- return nil356+ s.settleStream(st, ErrStreamCredit)
362 }357 }
363- st := s.lookupStream(m.Stream)358+ // Any other error means the stream already reached its terminal outcome;
364- if st == nil {359+ // its data is discarded.
365- // The bridge was settled between the tracker check and the lookup (a360+ return nil
366- // stream-local failure won the race); the chunk is discarded with it.361+}
367- return nil362+
363+// grantStreamCredit applies one client credit grant to its bridge.
364+func (s *serverSession) grantStreamCredit(m brokerwire.StreamWindowUpdate) error {
365+ st, err := s.streamFor(m.Stream)
366+ if st == nil || err != nil {
367+ return err
368 }368 }
369- switch err := st.deliver(m.Data); {369+ if err := st.pipe.granted(m.Credit); err != nil {
370- case err == nil:370+ s.settleStream(st, err)
371- return nil
372- case errors.Is(err, ErrStreamBackpressure):
373- s.settleStream(st, ErrStreamBackpressure)
374- return nil
375- default:
376- // The stream already reached its terminal outcome; its data is
377- // discarded.
378- return nil
379 }371 }
372+ return nil
373+}
374+
375+// streamFor classifies one stream-scoped frame and returns its live bridge. A
376+// frame for a retired stream, or one settled between the tracker check and the
377+// lookup, yields no bridge and no error; a frame for a stream that was never
378+// allocated, or before it was confirmed open, is a peer protocol violation.
379+func (s *serverSession) streamFor(id ports.BrokerStreamID) (*serverStream, error) {
380+ disposition, err := s.conn.StreamData(id)
381+ if err != nil {
382+ return nil, errors.Join(ErrProtocol, err)
383+ }
384+ if disposition == brokerwire.StreamDiscarded {
385+ return nil, nil
386+ }
387+ return s.lookupStream(id), nil
380 }388 }
381 389
382 // clientStream is the client-side half of one logical stream: the sessionwire390 // clientStream is the client-side half of one logical stream: the sessionwire
383-// client connection over the stream's bounded pipe, plus the terminal outcome a391+// client connection over the stream's credit-controlled pipe, plus the terminal outcome a
384 // ports.BrokerLogicalConnection exposes independently of reads.392 // ports.BrokerLogicalConnection exposes independently of reads.
385 type clientStream struct {393 type clientStream struct {
386 ports.ClientConnection394 ports.ClientConnection
@@ -407,7 +415,7 @@ func newClientStream(c *client, id ports.BrokerStreamID) (*clientStream, error)
407 opened: make(chan error, 1),415 opened: make(chan error, 1),
408 done: make(chan struct{}),416 done: make(chan struct{}),
409 }417 }
410- st.pipe = newStreamPipe(c.cfg.StreamInboundChunks, c.cfg.StreamInboundBytes, st.sendChunk)418+ st.pipe = newStreamPipe(int(c.ceilings.StreamChunkLimit), st.sendFrame, st.grantCredit)
411 st.carriage = newCarriage(st.pipe)419 st.carriage = newCarriage(st.pipe)
412 st.ClientConnection = sessionwire.NewClientConnection(st.carriage)420 st.ClientConnection = sessionwire.NewClientConnection(st.carriage)
413 if st.ClientConnection == nil {421 if st.ClientConnection == nil {
@@ -416,23 +424,20 @@ func newClientStream(c *client, id ports.BrokerStreamID) (*clientStream, error)
416 return st, nil424 return st, nil
417 }425 }
418 426
419-// sendChunk splits one outbound client byte slice under the negotiated stream427+// sendFrame writes one credited outbound frame to the broker.
420-// chunk ceiling.428+func (st *clientStream) sendFrame(data []byte) error {
421-func (st *clientStream) sendChunk(data []byte) error {429+ return st.client.send(brokerwire.ClientStreamData{
422- ceiling := int(st.client.ceilings.StreamChunkLimit)430+ Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Data: data,
423- if ceiling <= 0 {431+ })
424- return ErrConfig432+}
425- }433+
426- for len(data) > 0 {434+// grantCredit returns consumed inbound credit to the broker. It is written
427- n := min(len(data), ceiling)435+// synchronously because a dropped grant would stall the stream forever; the
428- if err := st.client.send(brokerwire.ClientStreamData{436+// write never waits long, since the broker's reader never blocks on a stream.
429- Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Data: data[:n],437+func (st *clientStream) grantCredit(credit uint64) error {
430- }); err != nil {438+ return st.client.send(brokerwire.StreamWindowUpdate{
431- return err439+ Epoch: st.client.scope.Epoch, Connection: st.client.scope.Connection, Stream: st.id, Credit: credit,
432- }440+ })
433- data = data[n:]
434- }
435- return nil
436 }441 }
437 442
438 // deliver accepts one inbound server chunk.443 // deliver accepts one inbound server chunk.
M internal/adapters/brokerwire/codec.go +59 -2
@@ -241,6 +241,17 @@ func encodeClientEnvelope(message ClientMessage, maxChunkBytes uint64) (*wire.Br
241241 return nil, ErrInvalidMessage
242242 }
243243 return encodeClientEnvelope(*m, maxChunkBytes)
244+ case StreamWindowUpdate:
245+ converted, err := streamWindowUpdateToWire(m)
246+ if err != nil {
247+ return nil, err
248+ }
249+ return &wire.BrokerClientEnvelope{Payload: &wire.BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: converted}}, nil
250+ case *StreamWindowUpdate:
251+ if m == nil {
252+ return nil, ErrInvalidMessage
253+ }
254+ return encodeClientEnvelope(*m, maxChunkBytes)
244255 case StartPreview:
245256 converted, err := startPreviewToWire(m)
246257 if err != nil {
@@ -308,6 +319,8 @@ func decodeClientEnvelope(envelope *wire.BrokerClientEnvelope, maxChunkBytes uin
308319 return nil, ErrInvalidMessage
309320 }
310321 return CloseStream{Epoch: epoch, Connection: connection, Stream: stream}, nil
322+ case *wire.BrokerClientEnvelope_StreamWindowUpdate:
323+ return streamWindowUpdateFromWire(payload.StreamWindowUpdate)
311324 case *wire.BrokerClientEnvelope_StartPreview:
312325 return startPreviewFromWire(payload.StartPreview)
313326 case *wire.BrokerClientEnvelope_CancelPreview:
@@ -707,6 +720,33 @@ func openStreamFromWire(message *wire.OpenStream) (OpenStream, error) {
707720 return candidate, nil
708721 }
709722
723+// streamWindowUpdateToWire refuses a zero or above-window grant: credit is
724+// returned only for consumed data, so a valid grant never exceeds one window.
725+func streamWindowUpdateToWire(m StreamWindowUpdate) (*wire.StreamWindowUpdate, error) {
726+ if m.Epoch == 0 || m.Connection.Validate() != nil || m.Stream.Validate() != nil {
727+ return nil, ErrInvalidMessage
728+ }
729+ if m.Credit == 0 || m.Credit > StreamWindowBytes {
730+ return nil, ErrInvalidMessage
731+ }
732+ return &wire.StreamWindowUpdate{Ref: refToWire(m.Epoch, m.Connection, m.Stream), Credit: m.Credit}, nil
733+}
734+
735+func streamWindowUpdateFromWire(message *wire.StreamWindowUpdate) (StreamWindowUpdate, error) {
736+ if message == nil {
737+ return StreamWindowUpdate{}, ErrInvalidMessage
738+ }
739+ epoch, connection, stream, err := refFromWire(message.GetRef())
740+ if err != nil {
741+ return StreamWindowUpdate{}, ErrInvalidMessage
742+ }
743+ credit := message.GetCredit()
744+ if credit == 0 || credit > StreamWindowBytes {
745+ return StreamWindowUpdate{}, ErrInvalidMessage
746+ }
747+ return StreamWindowUpdate{Epoch: epoch, Connection: connection, Stream: stream, Credit: credit}, nil
748+}
749+
710750 func clientStreamDataToWire(m ClientStreamData, maxChunkBytes uint64) (*wire.ClientStreamData, error) {
711751 if m.Epoch == 0 {
712752 return nil, ErrInvalidMessage
@@ -751,7 +791,9 @@ func clientStreamDataFromWire(message *wire.ClientStreamData, maxChunkBytes uint
751791 if len(data) == 0 {
752792 return ClientStreamData{}, ErrInvalidMessage
753793 }
754- return ClientStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: append([]byte(nil), data...)}, nil
794+ // proto.Unmarshal copies bytes fields out of the payload, so the caller
795+ // owns data without another copy.
796+ return ClientStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: data}, nil
755797 }
756798
757799 func encodeServerEnvelope(message ServerMessage, maxChunkBytes uint64) (*wire.BrokerServerEnvelope, error) {
@@ -828,6 +870,17 @@ func encodeServerEnvelope(message ServerMessage, maxChunkBytes uint64) (*wire.Br
828870 return nil, ErrInvalidMessage
829871 }
830872 return encodeServerEnvelope(*m, maxChunkBytes)
873+ case StreamWindowUpdate:
874+ converted, err := streamWindowUpdateToWire(m)
875+ if err != nil {
876+ return nil, err
877+ }
878+ return &wire.BrokerServerEnvelope{Payload: &wire.BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: converted}}, nil
879+ case *StreamWindowUpdate:
880+ if m == nil {
881+ return nil, ErrInvalidMessage
882+ }
883+ return encodeServerEnvelope(*m, maxChunkBytes)
831884 case StreamClosed:
832885 converted, err := streamClosedToWire(m)
833886 if err != nil {
@@ -929,6 +982,8 @@ func decodeServerEnvelope(envelope *wire.BrokerServerEnvelope, maxChunkBytes uin
929982 return serverStreamDataFromWire(payload.ServerStreamData, maxChunkBytes)
930983 case *wire.BrokerServerEnvelope_StreamClosed:
931984 return streamClosedFromWire(payload.StreamClosed)
985+ case *wire.BrokerServerEnvelope_StreamWindowUpdate:
986+ return streamWindowUpdateFromWire(payload.StreamWindowUpdate)
932987 case *wire.BrokerServerEnvelope_Progress:
933988 return progressFromWire(payload.Progress)
934989 case *wire.BrokerServerEnvelope_BrokerErrorMessage:
@@ -1492,7 +1547,9 @@ func serverStreamDataFromWire(message *wire.ServerStreamData, maxChunkBytes uint
14921547 if len(data) == 0 {
14931548 return ServerStreamData{}, ErrInvalidMessage
14941549 }
1495- return ServerStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: append([]byte(nil), data...)}, nil
1550+ // proto.Unmarshal copies bytes fields out of the payload, so the caller
1551+ // owns data without another copy.
1552+ return ServerStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: data}, nil
14961553 }
14971554
14981555 func streamClosedToWire(m StreamClosed) (*wire.StreamClosed, error) {
@@ -241,6 +241,17 @@ func encodeClientEnvelope(message ClientMessage, maxChunkBytes uint64) (*wire.Br
241 return nil, ErrInvalidMessage241 return nil, ErrInvalidMessage
242 }242 }
243 return encodeClientEnvelope(*m, maxChunkBytes)243 return encodeClientEnvelope(*m, maxChunkBytes)
244+ case StreamWindowUpdate:
245+ converted, err := streamWindowUpdateToWire(m)
246+ if err != nil {
247+ return nil, err
248+ }
249+ return &wire.BrokerClientEnvelope{Payload: &wire.BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: converted}}, nil
250+ case *StreamWindowUpdate:
251+ if m == nil {
252+ return nil, ErrInvalidMessage
253+ }
254+ return encodeClientEnvelope(*m, maxChunkBytes)
244 case StartPreview:255 case StartPreview:
245 converted, err := startPreviewToWire(m)256 converted, err := startPreviewToWire(m)
246 if err != nil {257 if err != nil {
@@ -308,6 +319,8 @@ func decodeClientEnvelope(envelope *wire.BrokerClientEnvelope, maxChunkBytes uin
308 return nil, ErrInvalidMessage319 return nil, ErrInvalidMessage
309 }320 }
310 return CloseStream{Epoch: epoch, Connection: connection, Stream: stream}, nil321 return CloseStream{Epoch: epoch, Connection: connection, Stream: stream}, nil
322+ case *wire.BrokerClientEnvelope_StreamWindowUpdate:
323+ return streamWindowUpdateFromWire(payload.StreamWindowUpdate)
311 case *wire.BrokerClientEnvelope_StartPreview:324 case *wire.BrokerClientEnvelope_StartPreview:
312 return startPreviewFromWire(payload.StartPreview)325 return startPreviewFromWire(payload.StartPreview)
313 case *wire.BrokerClientEnvelope_CancelPreview:326 case *wire.BrokerClientEnvelope_CancelPreview:
@@ -707,6 +720,33 @@ func openStreamFromWire(message *wire.OpenStream) (OpenStream, error) {
707 return candidate, nil720 return candidate, nil
708 }721 }
709 722
723+// streamWindowUpdateToWire refuses a zero or above-window grant: credit is
724+// returned only for consumed data, so a valid grant never exceeds one window.
725+func streamWindowUpdateToWire(m StreamWindowUpdate) (*wire.StreamWindowUpdate, error) {
726+ if m.Epoch == 0 || m.Connection.Validate() != nil || m.Stream.Validate() != nil {
727+ return nil, ErrInvalidMessage
728+ }
729+ if m.Credit == 0 || m.Credit > StreamWindowBytes {
730+ return nil, ErrInvalidMessage
731+ }
732+ return &wire.StreamWindowUpdate{Ref: refToWire(m.Epoch, m.Connection, m.Stream), Credit: m.Credit}, nil
733+}
734+
735+func streamWindowUpdateFromWire(message *wire.StreamWindowUpdate) (StreamWindowUpdate, error) {
736+ if message == nil {
737+ return StreamWindowUpdate{}, ErrInvalidMessage
738+ }
739+ epoch, connection, stream, err := refFromWire(message.GetRef())
740+ if err != nil {
741+ return StreamWindowUpdate{}, ErrInvalidMessage
742+ }
743+ credit := message.GetCredit()
744+ if credit == 0 || credit > StreamWindowBytes {
745+ return StreamWindowUpdate{}, ErrInvalidMessage
746+ }
747+ return StreamWindowUpdate{Epoch: epoch, Connection: connection, Stream: stream, Credit: credit}, nil
748+}
749+
710 func clientStreamDataToWire(m ClientStreamData, maxChunkBytes uint64) (*wire.ClientStreamData, error) {750 func clientStreamDataToWire(m ClientStreamData, maxChunkBytes uint64) (*wire.ClientStreamData, error) {
711 if m.Epoch == 0 {751 if m.Epoch == 0 {
712 return nil, ErrInvalidMessage752 return nil, ErrInvalidMessage
@@ -751,7 +791,9 @@ func clientStreamDataFromWire(message *wire.ClientStreamData, maxChunkBytes uint
751 if len(data) == 0 {791 if len(data) == 0 {
752 return ClientStreamData{}, ErrInvalidMessage792 return ClientStreamData{}, ErrInvalidMessage
753 }793 }
754- return ClientStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: append([]byte(nil), data...)}, nil794+ // proto.Unmarshal copies bytes fields out of the payload, so the caller
795+ // owns data without another copy.
796+ return ClientStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: data}, nil
755 }797 }
756 798
757 func encodeServerEnvelope(message ServerMessage, maxChunkBytes uint64) (*wire.BrokerServerEnvelope, error) {799 func encodeServerEnvelope(message ServerMessage, maxChunkBytes uint64) (*wire.BrokerServerEnvelope, error) {
@@ -828,6 +870,17 @@ func encodeServerEnvelope(message ServerMessage, maxChunkBytes uint64) (*wire.Br
828 return nil, ErrInvalidMessage870 return nil, ErrInvalidMessage
829 }871 }
830 return encodeServerEnvelope(*m, maxChunkBytes)872 return encodeServerEnvelope(*m, maxChunkBytes)
873+ case StreamWindowUpdate:
874+ converted, err := streamWindowUpdateToWire(m)
875+ if err != nil {
876+ return nil, err
877+ }
878+ return &wire.BrokerServerEnvelope{Payload: &wire.BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: converted}}, nil
879+ case *StreamWindowUpdate:
880+ if m == nil {
881+ return nil, ErrInvalidMessage
882+ }
883+ return encodeServerEnvelope(*m, maxChunkBytes)
831 case StreamClosed:884 case StreamClosed:
832 converted, err := streamClosedToWire(m)885 converted, err := streamClosedToWire(m)
833 if err != nil {886 if err != nil {
@@ -929,6 +982,8 @@ func decodeServerEnvelope(envelope *wire.BrokerServerEnvelope, maxChunkBytes uin
929 return serverStreamDataFromWire(payload.ServerStreamData, maxChunkBytes)982 return serverStreamDataFromWire(payload.ServerStreamData, maxChunkBytes)
930 case *wire.BrokerServerEnvelope_StreamClosed:983 case *wire.BrokerServerEnvelope_StreamClosed:
931 return streamClosedFromWire(payload.StreamClosed)984 return streamClosedFromWire(payload.StreamClosed)
985+ case *wire.BrokerServerEnvelope_StreamWindowUpdate:
986+ return streamWindowUpdateFromWire(payload.StreamWindowUpdate)
932 case *wire.BrokerServerEnvelope_Progress:987 case *wire.BrokerServerEnvelope_Progress:
933 return progressFromWire(payload.Progress)988 return progressFromWire(payload.Progress)
934 case *wire.BrokerServerEnvelope_BrokerErrorMessage:989 case *wire.BrokerServerEnvelope_BrokerErrorMessage:
@@ -1492,7 +1547,9 @@ func serverStreamDataFromWire(message *wire.ServerStreamData, maxChunkBytes uint
1492 if len(data) == 0 {1547 if len(data) == 0 {
1493 return ServerStreamData{}, ErrInvalidMessage1548 return ServerStreamData{}, ErrInvalidMessage
1494 }1549 }
1495- return ServerStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: append([]byte(nil), data...)}, nil1550+ // proto.Unmarshal copies bytes fields out of the payload, so the caller
1551+ // owns data without another copy.
1552+ return ServerStreamData{Epoch: epoch, Connection: connection, Stream: stream, Data: data}, nil
1496 }1553 }
1497 1554
1498 func streamClosedToWire(m StreamClosed) (*wire.StreamClosed, error) {1555 func streamClosedToWire(m StreamClosed) (*wire.StreamClosed, error) {
M internal/adapters/brokerwire/codec_test.go +55 -1
@@ -360,8 +360,9 @@ func TestBrokerClientVariants(t *testing.T) {
360360 {"update_host_policy", UpdateHostPolicy{Epoch: 7, Connection: connection, Operation: operation, Registration: testRegistration(), Policy: testPolicy()}, 111},
361361 {"start_preview", testStartPreview(connection), 112},
362362 {"cancel_preview", CancelPreview{Epoch: 7, Connection: connection, Generation: 3}, 113},
363+ {"stream_window_update", StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: 4096}, 114},
363364 }
364- require.Len(t, messages, 13)
365+ require.Len(t, messages, 14)
365366 for _, tc := range messages {
366367 t.Run(tc.name, func(t *testing.T) {
367368 raw := mustEncodeClient(t, tc.message)
@@ -376,6 +377,22 @@ func TestBrokerClientVariants(t *testing.T) {
376377 }
377378 }
378379
380+// TestBrokerStreamDataOwnsItsBytes proves decoded stream data never aliases the
381+// payload: receivers reuse their buffers, and brokeripc queues Data as is.
382+func TestBrokerStreamDataOwnsItsBytes(t *testing.T) {
383+ connection := testConnectionID(0x11)
384+ client := mustEncodeClient(t, ClientStreamData{Epoch: 7, Connection: connection, Stream: 3, Data: []byte("frame")})
385+ server := mustEncodeServer(t, ServerStreamData{Epoch: 7, Connection: connection, Stream: 3, Data: []byte("frame")})
386+ decodedClient, err := DecodeClient(client, testEnvelopeCeiling, testChunkCeiling)
387+ require.NoError(t, err)
388+ decodedServer, err := DecodeServer(server, testEnvelopeCeiling, testChunkCeiling)
389+ require.NoError(t, err)
390+ clear(client)
391+ clear(server)
392+ require.Equal(t, []byte("frame"), decodedClient.(ClientStreamData).Data)
393+ require.Equal(t, []byte("frame"), decodedServer.(ServerStreamData).Data)
394+}
395+
379396 // TestBrokerOpenStreamAdmissionVariants pins the attachment-admission
380397 // contract: exact, create-named, and create-ephemeral round-trip, control
381398 // carries none, and wrong admission/purpose/name/target combinations are
@@ -653,6 +670,7 @@ func TestBrokerServerVariants(t *testing.T) {
653670 {"shutdown", Shutdown{Epoch: 7, Connection: connection, Reason: ShutdownTerminating, Text: "stopping"}, 209},
654671 {"preview_publication", testPreviewPublication(connection), 210},
655672 {"preview_publication_error", PreviewPublication{Epoch: 7, Connection: connection, Generation: 5, Error: testErrorDetail(), HasError: true}, 210},
673+ {"stream_window_update", StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: StreamWindowBytes}, 211},
656674 }
657675 for _, tc := range messages {
658676 t.Run(tc.name, func(t *testing.T) {
@@ -924,6 +942,40 @@ func TestBrokerValidationNegatives(t *testing.T) {
924942 _, err := EncodeClient(CloseStream{Epoch: 7, Connection: connection}, testEnvelopeCeiling, testChunkCeiling)
925943 require.ErrorIs(t, err, ErrInvalidMessage)
926944 })
945+ t.Run("stream window update bounds", func(t *testing.T) {
946+ valid := StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: 1}
947+ cases := map[string]func(*StreamWindowUpdate){
948+ "zero credit": func(m *StreamWindowUpdate) { m.Credit = 0 },
949+ "credit above window": func(m *StreamWindowUpdate) { m.Credit = StreamWindowBytes + 1 },
950+ "zero epoch": func(m *StreamWindowUpdate) { m.Epoch = 0 },
951+ "zero stream": func(m *StreamWindowUpdate) { m.Stream = 0 },
952+ }
953+ for name, mutate := range cases {
954+ t.Run(name, func(t *testing.T) {
955+ bad := valid
956+ mutate(&bad)
957+ _, err := EncodeClient(bad, testEnvelopeCeiling, testChunkCeiling)
958+ require.ErrorIs(t, err, ErrInvalidMessage)
959+ _, err = EncodeServer(bad, testEnvelopeCeiling, testChunkCeiling)
960+ require.ErrorIs(t, err, ErrInvalidMessage)
961+ })
962+ }
963+ // The decoder refuses the same out-of-range credit a peer could put
964+ // on the wire directly.
965+ for name, credit := range map[string]uint64{"zero credit": 0, "credit above window": StreamWindowBytes + 1} {
966+ t.Run("decode "+name, func(t *testing.T) {
967+ message := &wire.StreamWindowUpdate{Ref: refToWire(7, connection, 3), Credit: credit}
968+ client, err := proto.Marshal(&wire.BrokerClientEnvelope{Payload: &wire.BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: message}})
969+ require.NoError(t, err)
970+ _, err = DecodeClient(client, testEnvelopeCeiling, testChunkCeiling)
971+ require.ErrorIs(t, err, ErrInvalidMessage)
972+ server, err := proto.Marshal(&wire.BrokerServerEnvelope{Payload: &wire.BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: message}})
973+ require.NoError(t, err)
974+ _, err = DecodeServer(server, testEnvelopeCeiling, testChunkCeiling)
975+ require.ErrorIs(t, err, ErrInvalidMessage)
976+ })
977+ }
978+ })
927979 t.Run("error detail never travels without HasError", func(t *testing.T) {
928980 detail := testErrorDetail()
929981 // HasError=false is exactly the absent detail, whatever the outcome
@@ -1254,6 +1306,7 @@ func fuzzClientSeeds(t testing.TB) [][]byte {
12541306 OpenStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Purpose: ports.BrokerStreamAttachment, Admission: ports.BrokerAdmissionExact, Endpoint: "dev@host:22", Registration: testRegistration(), Target: testTarget(), Policy: testPolicy(), StartMode: ports.BrokerDaemonStartIfNeeded},
12551307 ClientStreamData{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Data: []byte("frame")},
12561308 CloseStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3},
1309+ StreamWindowUpdate{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Credit: 4096},
12571310 } {
12581311 raw, err := EncodeClient(message, testEnvelopeCeiling, testChunkCeiling)
12591312 require.NoError(t, err)
@@ -1272,6 +1325,7 @@ func fuzzServerSeeds(t testing.TB) [][]byte {
12721325 OperationResult{Epoch: 7, Connection: testConnectionID(0x11), Operation: testOperationID(0x14), Outcome: ports.BrokerOutcomeOK, Registration: testRegistration()},
12731326 BrokerErrorMessage{Epoch: 7, Connection: testConnectionID(0x11), Error: testErrorDetail()},
12741327 Shutdown{Epoch: 7, Connection: testConnectionID(0x11), Reason: ShutdownTerminating},
1328+ StreamWindowUpdate{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Credit: 4096},
12751329 } {
12761330 raw, err := EncodeServer(message, testEnvelopeCeiling, testChunkCeiling)
12771331 require.NoError(t, err)
@@ -360,8 +360,9 @@ func TestBrokerClientVariants(t *testing.T) {
360 {"update_host_policy", UpdateHostPolicy{Epoch: 7, Connection: connection, Operation: operation, Registration: testRegistration(), Policy: testPolicy()}, 111},360 {"update_host_policy", UpdateHostPolicy{Epoch: 7, Connection: connection, Operation: operation, Registration: testRegistration(), Policy: testPolicy()}, 111},
361 {"start_preview", testStartPreview(connection), 112},361 {"start_preview", testStartPreview(connection), 112},
362 {"cancel_preview", CancelPreview{Epoch: 7, Connection: connection, Generation: 3}, 113},362 {"cancel_preview", CancelPreview{Epoch: 7, Connection: connection, Generation: 3}, 113},
363+ {"stream_window_update", StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: 4096}, 114},
363 }364 }
364- require.Len(t, messages, 13)365+ require.Len(t, messages, 14)
365 for _, tc := range messages {366 for _, tc := range messages {
366 t.Run(tc.name, func(t *testing.T) {367 t.Run(tc.name, func(t *testing.T) {
367 raw := mustEncodeClient(t, tc.message)368 raw := mustEncodeClient(t, tc.message)
@@ -376,6 +377,22 @@ func TestBrokerClientVariants(t *testing.T) {
376 }377 }
377 }378 }
378 379
380+// TestBrokerStreamDataOwnsItsBytes proves decoded stream data never aliases the
381+// payload: receivers reuse their buffers, and brokeripc queues Data as is.
382+func TestBrokerStreamDataOwnsItsBytes(t *testing.T) {
383+ connection := testConnectionID(0x11)
384+ client := mustEncodeClient(t, ClientStreamData{Epoch: 7, Connection: connection, Stream: 3, Data: []byte("frame")})
385+ server := mustEncodeServer(t, ServerStreamData{Epoch: 7, Connection: connection, Stream: 3, Data: []byte("frame")})
386+ decodedClient, err := DecodeClient(client, testEnvelopeCeiling, testChunkCeiling)
387+ require.NoError(t, err)
388+ decodedServer, err := DecodeServer(server, testEnvelopeCeiling, testChunkCeiling)
389+ require.NoError(t, err)
390+ clear(client)
391+ clear(server)
392+ require.Equal(t, []byte("frame"), decodedClient.(ClientStreamData).Data)
393+ require.Equal(t, []byte("frame"), decodedServer.(ServerStreamData).Data)
394+}
395+
379 // TestBrokerOpenStreamAdmissionVariants pins the attachment-admission396 // TestBrokerOpenStreamAdmissionVariants pins the attachment-admission
380 // contract: exact, create-named, and create-ephemeral round-trip, control397 // contract: exact, create-named, and create-ephemeral round-trip, control
381 // carries none, and wrong admission/purpose/name/target combinations are398 // carries none, and wrong admission/purpose/name/target combinations are
@@ -653,6 +670,7 @@ func TestBrokerServerVariants(t *testing.T) {
653 {"shutdown", Shutdown{Epoch: 7, Connection: connection, Reason: ShutdownTerminating, Text: "stopping"}, 209},670 {"shutdown", Shutdown{Epoch: 7, Connection: connection, Reason: ShutdownTerminating, Text: "stopping"}, 209},
654 {"preview_publication", testPreviewPublication(connection), 210},671 {"preview_publication", testPreviewPublication(connection), 210},
655 {"preview_publication_error", PreviewPublication{Epoch: 7, Connection: connection, Generation: 5, Error: testErrorDetail(), HasError: true}, 210},672 {"preview_publication_error", PreviewPublication{Epoch: 7, Connection: connection, Generation: 5, Error: testErrorDetail(), HasError: true}, 210},
673+ {"stream_window_update", StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: StreamWindowBytes}, 211},
656 }674 }
657 for _, tc := range messages {675 for _, tc := range messages {
658 t.Run(tc.name, func(t *testing.T) {676 t.Run(tc.name, func(t *testing.T) {
@@ -924,6 +942,40 @@ func TestBrokerValidationNegatives(t *testing.T) {
924 _, err := EncodeClient(CloseStream{Epoch: 7, Connection: connection}, testEnvelopeCeiling, testChunkCeiling)942 _, err := EncodeClient(CloseStream{Epoch: 7, Connection: connection}, testEnvelopeCeiling, testChunkCeiling)
925 require.ErrorIs(t, err, ErrInvalidMessage)943 require.ErrorIs(t, err, ErrInvalidMessage)
926 })944 })
945+ t.Run("stream window update bounds", func(t *testing.T) {
946+ valid := StreamWindowUpdate{Epoch: 7, Connection: connection, Stream: 3, Credit: 1}
947+ cases := map[string]func(*StreamWindowUpdate){
948+ "zero credit": func(m *StreamWindowUpdate) { m.Credit = 0 },
949+ "credit above window": func(m *StreamWindowUpdate) { m.Credit = StreamWindowBytes + 1 },
950+ "zero epoch": func(m *StreamWindowUpdate) { m.Epoch = 0 },
951+ "zero stream": func(m *StreamWindowUpdate) { m.Stream = 0 },
952+ }
953+ for name, mutate := range cases {
954+ t.Run(name, func(t *testing.T) {
955+ bad := valid
956+ mutate(&bad)
957+ _, err := EncodeClient(bad, testEnvelopeCeiling, testChunkCeiling)
958+ require.ErrorIs(t, err, ErrInvalidMessage)
959+ _, err = EncodeServer(bad, testEnvelopeCeiling, testChunkCeiling)
960+ require.ErrorIs(t, err, ErrInvalidMessage)
961+ })
962+ }
963+ // The decoder refuses the same out-of-range credit a peer could put
964+ // on the wire directly.
965+ for name, credit := range map[string]uint64{"zero credit": 0, "credit above window": StreamWindowBytes + 1} {
966+ t.Run("decode "+name, func(t *testing.T) {
967+ message := &wire.StreamWindowUpdate{Ref: refToWire(7, connection, 3), Credit: credit}
968+ client, err := proto.Marshal(&wire.BrokerClientEnvelope{Payload: &wire.BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: message}})
969+ require.NoError(t, err)
970+ _, err = DecodeClient(client, testEnvelopeCeiling, testChunkCeiling)
971+ require.ErrorIs(t, err, ErrInvalidMessage)
972+ server, err := proto.Marshal(&wire.BrokerServerEnvelope{Payload: &wire.BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: message}})
973+ require.NoError(t, err)
974+ _, err = DecodeServer(server, testEnvelopeCeiling, testChunkCeiling)
975+ require.ErrorIs(t, err, ErrInvalidMessage)
976+ })
977+ }
978+ })
927 t.Run("error detail never travels without HasError", func(t *testing.T) {979 t.Run("error detail never travels without HasError", func(t *testing.T) {
928 detail := testErrorDetail()980 detail := testErrorDetail()
929 // HasError=false is exactly the absent detail, whatever the outcome981 // HasError=false is exactly the absent detail, whatever the outcome
@@ -1254,6 +1306,7 @@ func fuzzClientSeeds(t testing.TB) [][]byte {
1254 OpenStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Purpose: ports.BrokerStreamAttachment, Admission: ports.BrokerAdmissionExact, Endpoint: "dev@host:22", Registration: testRegistration(), Target: testTarget(), Policy: testPolicy(), StartMode: ports.BrokerDaemonStartIfNeeded},1306 OpenStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Purpose: ports.BrokerStreamAttachment, Admission: ports.BrokerAdmissionExact, Endpoint: "dev@host:22", Registration: testRegistration(), Target: testTarget(), Policy: testPolicy(), StartMode: ports.BrokerDaemonStartIfNeeded},
1255 ClientStreamData{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Data: []byte("frame")},1307 ClientStreamData{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Data: []byte("frame")},
1256 CloseStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3},1308 CloseStream{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3},
1309+ StreamWindowUpdate{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Credit: 4096},
1257 } {1310 } {
1258 raw, err := EncodeClient(message, testEnvelopeCeiling, testChunkCeiling)1311 raw, err := EncodeClient(message, testEnvelopeCeiling, testChunkCeiling)
1259 require.NoError(t, err)1312 require.NoError(t, err)
@@ -1272,6 +1325,7 @@ func fuzzServerSeeds(t testing.TB) [][]byte {
1272 OperationResult{Epoch: 7, Connection: testConnectionID(0x11), Operation: testOperationID(0x14), Outcome: ports.BrokerOutcomeOK, Registration: testRegistration()},1325 OperationResult{Epoch: 7, Connection: testConnectionID(0x11), Operation: testOperationID(0x14), Outcome: ports.BrokerOutcomeOK, Registration: testRegistration()},
1273 BrokerErrorMessage{Epoch: 7, Connection: testConnectionID(0x11), Error: testErrorDetail()},1326 BrokerErrorMessage{Epoch: 7, Connection: testConnectionID(0x11), Error: testErrorDetail()},
1274 Shutdown{Epoch: 7, Connection: testConnectionID(0x11), Reason: ShutdownTerminating},1327 Shutdown{Epoch: 7, Connection: testConnectionID(0x11), Reason: ShutdownTerminating},
1328+ StreamWindowUpdate{Epoch: 7, Connection: testConnectionID(0x11), Stream: 3, Credit: 4096},
1275 } {1329 } {
1276 raw, err := EncodeServer(message, testEnvelopeCeiling, testChunkCeiling)1330 raw, err := EncodeServer(message, testEnvelopeCeiling, testChunkCeiling)
1277 require.NoError(t, err)1331 require.NoError(t, err)
M internal/adapters/brokerwire/limits.go +14 -0
@@ -39,8 +39,22 @@ const (
3939 // MaxStreamChunkBytes is the largest legal stream chunk ceiling:
4040 // 64 KiB per opaque stream frame.
4141 MaxStreamChunkBytes uint64 = 64 << 10
42+
43+ // StreamWindowBytes is the stream-data credit each side of one logical
44+ // stream grants its peer per direction. A sender spends StreamChunkCredit
45+ // per data frame and waits when it has too little, so the receiver never
46+ // queues more than this per stream; StreamWindowUpdate returns the credit
47+ // of consumed frames.
48+ StreamWindowBytes uint64 = 4 << 20
49+ // StreamChunkCreditOverhead is the fixed credit one data frame costs on top
50+ // of its payload, so a burst of tiny frames is bounded in count as well.
51+ StreamChunkCreditOverhead uint64 = 64
4252 )
4353
54+// StreamChunkCredit is the flow-control cost of one stream data frame of n
55+// payload bytes.
56+func StreamChunkCredit(n int) uint64 { return uint64(n) + StreamChunkCreditOverhead }
57+
4458 // Ceilings are the immutable per-connection ceilings negotiated once by the
4559 // broker preamble. MaxReceiveEnvelopeBytes bounds every envelope this side
4660 // accepts; StreamChunkLimit bounds one opaque stream data frame in either
@@ -39,8 +39,22 @@ const (
39 // MaxStreamChunkBytes is the largest legal stream chunk ceiling:39 // MaxStreamChunkBytes is the largest legal stream chunk ceiling:
40 // 64 KiB per opaque stream frame.40 // 64 KiB per opaque stream frame.
41 MaxStreamChunkBytes uint64 = 64 << 1041 MaxStreamChunkBytes uint64 = 64 << 10
42+
43+ // StreamWindowBytes is the stream-data credit each side of one logical
44+ // stream grants its peer per direction. A sender spends StreamChunkCredit
45+ // per data frame and waits when it has too little, so the receiver never
46+ // queues more than this per stream; StreamWindowUpdate returns the credit
47+ // of consumed frames.
48+ StreamWindowBytes uint64 = 4 << 20
49+ // StreamChunkCreditOverhead is the fixed credit one data frame costs on top
50+ // of its payload, so a burst of tiny frames is bounded in count as well.
51+ StreamChunkCreditOverhead uint64 = 64
42 )52 )
43 53
54+// StreamChunkCredit is the flow-control cost of one stream data frame of n
55+// payload bytes.
56+func StreamChunkCredit(n int) uint64 { return uint64(n) + StreamChunkCreditOverhead }
57+
44 // Ceilings are the immutable per-connection ceilings negotiated once by the58 // Ceilings are the immutable per-connection ceilings negotiated once by the
45 // broker preamble. MaxReceiveEnvelopeBytes bounds every envelope this side59 // broker preamble. MaxReceiveEnvelopeBytes bounds every envelope this side
46 // accepts; StreamChunkLimit bounds one opaque stream data frame in either60 // accepts; StreamChunkLimit bounds one opaque stream data frame in either
M internal/adapters/brokerwire/messages.go +13 -0
@@ -165,6 +165,19 @@ type CloseStream struct {
165165
166166 func (CloseStream) brokerClientMessage() {}
167167
168+// StreamWindowUpdate grants the peer Credit more stream-data bytes on one
169+// logical stream after the receiver consumed them. It travels in both
170+// directions: each side grants credit for the data it receives.
171+type StreamWindowUpdate struct {
172+ Epoch ports.BrokerEpoch
173+ Connection ports.BrokerConnectionID
174+ Stream ports.BrokerStreamID
175+ Credit uint64
176+}
177+
178+func (StreamWindowUpdate) brokerClientMessage() {}
179+func (StreamWindowUpdate) brokerServerMessage() {}
180+
168181 // StartPreview starts or replaces one connection-scoped live preview.
169182 // Generation is the connection-scoped preview authority; Route names the
170183 // observed daemon (the broker allocates the stream) and Preview is the bounded
@@ -165,6 +165,19 @@ type CloseStream struct {
165 165
166 func (CloseStream) brokerClientMessage() {}166 func (CloseStream) brokerClientMessage() {}
167 167
168+// StreamWindowUpdate grants the peer Credit more stream-data bytes on one
169+// logical stream after the receiver consumed them. It travels in both
170+// directions: each side grants credit for the data it receives.
171+type StreamWindowUpdate struct {
172+ Epoch ports.BrokerEpoch
173+ Connection ports.BrokerConnectionID
174+ Stream ports.BrokerStreamID
175+ Credit uint64
176+}
177+
178+func (StreamWindowUpdate) brokerClientMessage() {}
179+func (StreamWindowUpdate) brokerServerMessage() {}
180+
168 // StartPreview starts or replaces one connection-scoped live preview.181 // StartPreview starts or replaces one connection-scoped live preview.
169 // Generation is the connection-scoped preview authority; Route names the182 // Generation is the connection-scoped preview authority; Route names the
170 // observed daemon (the broker allocates the stream) and Preview is the bounded183 // observed daemon (the broker allocates the stream) and Preview is the bounded
M internal/protocol/session.go +8 -1
@@ -17,7 +17,7 @@ import (
1717 // daemonmux physical preamble conversation (multiplex.proto), whose
1818 // negotiated ceilings and accepted daemon binding ride the same wire
1919 // version as the session and broker conversations.
20-const Version uint16 = 65
20+const Version uint16 = 66
2121
2222 // HandshakeTimeout bounds every transport handshake from connect through the
2323 // first committed publication. It excludes the preceding client-local
@@ -33,6 +33,13 @@ const MaxOutputDataLen = (16 << 20) - 1 - 55 - (8 + 16 + 2 + 64 + 1 + 2 + 128 +
3333 // MaxOutputWindow caps output states sent before client acknowledgement.
3434 const MaxOutputWindow = 8
3535
36+// MaxOutputWindowBytes caps the Output data a daemon keeps unacknowledged per
37+// attachment, state frames and side effects together. A full window of large
38+// frames would otherwise overrun every bounded buffer between daemon and
39+// terminal. The daemon may always send one frame when none is unacknowledged,
40+// so a single frame up to MaxOutputDataLen still makes progress.
41+const MaxOutputWindowBytes = 2 << 20
42+
3643 // ConnectionCapabilities describes transport behavior relevant to session
3744 // flow without naming a concrete carriage.
3845 type ConnectionCapabilities struct {
@@ -17,7 +17,7 @@ import (
17 // daemonmux physical preamble conversation (multiplex.proto), whose17 // daemonmux physical preamble conversation (multiplex.proto), whose
18 // negotiated ceilings and accepted daemon binding ride the same wire18 // negotiated ceilings and accepted daemon binding ride the same wire
19 // version as the session and broker conversations.19 // version as the session and broker conversations.
20-const Version uint16 = 6520+const Version uint16 = 66
21 21
22 // HandshakeTimeout bounds every transport handshake from connect through the22 // HandshakeTimeout bounds every transport handshake from connect through the
23 // first committed publication. It excludes the preceding client-local23 // first committed publication. It excludes the preceding client-local
@@ -33,6 +33,13 @@ const MaxOutputDataLen = (16 << 20) - 1 - 55 - (8 + 16 + 2 + 64 + 1 + 2 + 128 +
33 // MaxOutputWindow caps output states sent before client acknowledgement.33 // MaxOutputWindow caps output states sent before client acknowledgement.
34 const MaxOutputWindow = 834 const MaxOutputWindow = 8
35 35
36+// MaxOutputWindowBytes caps the Output data a daemon keeps unacknowledged per
37+// attachment, state frames and side effects together. A full window of large
38+// frames would otherwise overrun every bounded buffer between daemon and
39+// terminal. The daemon may always send one frame when none is unacknowledged,
40+// so a single frame up to MaxOutputDataLen still makes progress.
41+const MaxOutputWindowBytes = 2 << 20
42+
36 // ConnectionCapabilities describes transport behavior relevant to session43 // ConnectionCapabilities describes transport behavior relevant to session
37 // flow without naming a concrete carriage.44 // flow without naming a concrete carriage.
38 type ConnectionCapabilities struct {45 type ConnectionCapabilities struct {
M internal/protocol/session_test.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 )
1111
1212 func TestVersionRemainsWireCompatible(t *testing.T) {
13- require.Equal(t, uint16(65), Version)
13+ require.Equal(t, uint16(66), Version)
1414 require.Equal(t, (16<<20)-407, MaxOutputDataLen)
1515 }
1616
@@ -10,7 +10,7 @@ import (
10 )10 )
11 11
12 func TestVersionRemainsWireCompatible(t *testing.T) {12 func TestVersionRemainsWireCompatible(t *testing.T) {
13- require.Equal(t, uint16(65), Version)13+ require.Equal(t, uint16(66), Version)
14 require.Equal(t, (16<<20)-407, MaxOutputDataLen)14 require.Equal(t, (16<<20)-407, MaxOutputDataLen)
15 }15 }
16 16
M internal/protocol/wire/broker.pb.go +262 -163
@@ -390,6 +390,7 @@ type BrokerClientEnvelope struct {
390390 // *BrokerClientEnvelope_UpdateHostPolicy
391391 // *BrokerClientEnvelope_StartPreview
392392 // *BrokerClientEnvelope_CancelPreview
393+ // *BrokerClientEnvelope_StreamWindowUpdate
393394 Payload isBrokerClientEnvelope_Payload `protobuf_oneof:"payload"`
394395 unknownFields protoimpl.UnknownFields
395396 sizeCache protoimpl.SizeCache
@@ -549,6 +550,15 @@ func (x *BrokerClientEnvelope) GetCancelPreview() *CancelPreview {
549550 return nil
550551 }
551552
553+func (x *BrokerClientEnvelope) GetStreamWindowUpdate() *StreamWindowUpdate {
554+ if x != nil {
555+ if x, ok := x.Payload.(*BrokerClientEnvelope_StreamWindowUpdate); ok {
556+ return x.StreamWindowUpdate
557+ }
558+ }
559+ return nil
560+}
561+
552562 type isBrokerClientEnvelope_Payload interface {
553563 isBrokerClientEnvelope_Payload()
554564 }
@@ -605,6 +615,10 @@ type BrokerClientEnvelope_CancelPreview struct {
605615 CancelPreview *CancelPreview `protobuf:"bytes,113,opt,name=cancel_preview,json=cancelPreview,proto3,oneof"`
606616 }
607617
618+type BrokerClientEnvelope_StreamWindowUpdate struct {
619+ StreamWindowUpdate *StreamWindowUpdate `protobuf:"bytes,114,opt,name=stream_window_update,json=streamWindowUpdate,proto3,oneof"`
620+}
621+
608622 func (*BrokerClientEnvelope_Register) isBrokerClientEnvelope_Payload() {}
609623
610624 func (*BrokerClientEnvelope_Subscribe) isBrokerClientEnvelope_Payload() {}
@@ -631,6 +645,8 @@ func (*BrokerClientEnvelope_StartPreview) isBrokerClientEnvelope_Payload() {}
631645
632646 func (*BrokerClientEnvelope_CancelPreview) isBrokerClientEnvelope_Payload() {}
633647
648+func (*BrokerClientEnvelope_StreamWindowUpdate) isBrokerClientEnvelope_Payload() {}
649+
634650 // BrokerServerEnvelope is the closed server-to-client broker union. Exactly
635651 // one variant must be set per envelope.
636652 type BrokerServerEnvelope struct {
@@ -647,6 +663,7 @@ type BrokerServerEnvelope struct {
647663 // *BrokerServerEnvelope_BrokerErrorMessage
648664 // *BrokerServerEnvelope_Shutdown
649665 // *BrokerServerEnvelope_PreviewPublication
666+ // *BrokerServerEnvelope_StreamWindowUpdate
650667 Payload isBrokerServerEnvelope_Payload `protobuf_oneof:"payload"`
651668 unknownFields protoimpl.UnknownFields
652669 sizeCache protoimpl.SizeCache
@@ -779,6 +796,15 @@ func (x *BrokerServerEnvelope) GetPreviewPublication() *PreviewPublication {
779796 return nil
780797 }
781798
799+func (x *BrokerServerEnvelope) GetStreamWindowUpdate() *StreamWindowUpdate {
800+ if x != nil {
801+ if x, ok := x.Payload.(*BrokerServerEnvelope_StreamWindowUpdate); ok {
802+ return x.StreamWindowUpdate
803+ }
804+ }
805+ return nil
806+}
807+
782808 type isBrokerServerEnvelope_Payload interface {
783809 isBrokerServerEnvelope_Payload()
784810 }
@@ -823,6 +849,10 @@ type BrokerServerEnvelope_PreviewPublication struct {
823849 PreviewPublication *PreviewPublication `protobuf:"bytes,210,opt,name=preview_publication,json=previewPublication,proto3,oneof"`
824850 }
825851
852+type BrokerServerEnvelope_StreamWindowUpdate struct {
853+ StreamWindowUpdate *StreamWindowUpdate `protobuf:"bytes,211,opt,name=stream_window_update,json=streamWindowUpdate,proto3,oneof"`
854+}
855+
826856 func (*BrokerServerEnvelope_Registered) isBrokerServerEnvelope_Payload() {}
827857
828858 func (*BrokerServerEnvelope_SnapshotPart) isBrokerServerEnvelope_Payload() {}
@@ -843,6 +873,8 @@ func (*BrokerServerEnvelope_Shutdown) isBrokerServerEnvelope_Payload() {}
843873
844874 func (*BrokerServerEnvelope_PreviewPublication) isBrokerServerEnvelope_Payload() {}
845875
876+func (*BrokerServerEnvelope_StreamWindowUpdate) isBrokerServerEnvelope_Payload() {}
877+
846878 // Register opens one client connection to the broker.
847879 type Register struct {
848880 state protoimpl.MessageState `protogen:"open.v1"`
@@ -1563,6 +1595,62 @@ func (x *CloseStream) GetRef() *BrokerStreamRef {
15631595 return nil
15641596 }
15651597
1598+// StreamWindowUpdate grants the peer credit to send more stream data on one
1599+// logical stream: the receiver consumed that much of its stream window. Each
1600+// direction of a stream has its own window, so a client grants credit for
1601+// ServerStreamData and the server grants credit for ClientStreamData.
1602+type StreamWindowUpdate struct {
1603+ state protoimpl.MessageState `protogen:"open.v1"`
1604+ Ref *BrokerStreamRef `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"`
1605+ Credit uint64 `protobuf:"varint,2,opt,name=credit,proto3" json:"credit,omitempty"`
1606+ unknownFields protoimpl.UnknownFields
1607+ sizeCache protoimpl.SizeCache
1608+}
1609+
1610+func (x *StreamWindowUpdate) Reset() {
1611+ *x = StreamWindowUpdate{}
1612+ mi := &file_broker_proto_msgTypes[18]
1613+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1614+ ms.StoreMessageInfo(mi)
1615+}
1616+
1617+func (x *StreamWindowUpdate) String() string {
1618+ return protoimpl.X.MessageStringOf(x)
1619+}
1620+
1621+func (*StreamWindowUpdate) ProtoMessage() {}
1622+
1623+func (x *StreamWindowUpdate) ProtoReflect() protoreflect.Message {
1624+ mi := &file_broker_proto_msgTypes[18]
1625+ if x != nil {
1626+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1627+ if ms.LoadMessageInfo() == nil {
1628+ ms.StoreMessageInfo(mi)
1629+ }
1630+ return ms
1631+ }
1632+ return mi.MessageOf(x)
1633+}
1634+
1635+// Deprecated: Use StreamWindowUpdate.ProtoReflect.Descriptor instead.
1636+func (*StreamWindowUpdate) Descriptor() ([]byte, []int) {
1637+ return file_broker_proto_rawDescGZIP(), []int{18}
1638+}
1639+
1640+func (x *StreamWindowUpdate) GetRef() *BrokerStreamRef {
1641+ if x != nil {
1642+ return x.Ref
1643+ }
1644+ return nil
1645+}
1646+
1647+func (x *StreamWindowUpdate) GetCredit() uint64 {
1648+ if x != nil {
1649+ return x.Credit
1650+ }
1651+ return 0
1652+}
1653+
15661654 // Registered confirms one accepted client connection and its assigned scope.
15671655 type Registered struct {
15681656 state protoimpl.MessageState `protogen:"open.v1"`
@@ -1575,7 +1663,7 @@ type Registered struct {
15751663
15761664 func (x *Registered) Reset() {
15771665 *x = Registered{}
1578- mi := &file_broker_proto_msgTypes[18]
1666+ mi := &file_broker_proto_msgTypes[19]
15791667 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
15801668 ms.StoreMessageInfo(mi)
15811669 }
@@ -1587,7 +1675,7 @@ func (x *Registered) String() string {
15871675 func (*Registered) ProtoMessage() {}
15881676
15891677 func (x *Registered) ProtoReflect() protoreflect.Message {
1590- mi := &file_broker_proto_msgTypes[18]
1678+ mi := &file_broker_proto_msgTypes[19]
15911679 if x != nil {
15921680 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
15931681 if ms.LoadMessageInfo() == nil {
@@ -1600,7 +1688,7 @@ func (x *Registered) ProtoReflect() protoreflect.Message {
16001688
16011689 // Deprecated: Use Registered.ProtoReflect.Descriptor instead.
16021690 func (*Registered) Descriptor() ([]byte, []int) {
1603- return file_broker_proto_rawDescGZIP(), []int{18}
1691+ return file_broker_proto_rawDescGZIP(), []int{19}
16041692 }
16051693
16061694 func (x *Registered) GetScope() *BrokerScope {
@@ -1647,7 +1735,7 @@ type SnapshotPart struct {
16471735
16481736 func (x *SnapshotPart) Reset() {
16491737 *x = SnapshotPart{}
1650- mi := &file_broker_proto_msgTypes[19]
1738+ mi := &file_broker_proto_msgTypes[20]
16511739 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
16521740 ms.StoreMessageInfo(mi)
16531741 }
@@ -1659,7 +1747,7 @@ func (x *SnapshotPart) String() string {
16591747 func (*SnapshotPart) ProtoMessage() {}
16601748
16611749 func (x *SnapshotPart) ProtoReflect() protoreflect.Message {
1662- mi := &file_broker_proto_msgTypes[19]
1750+ mi := &file_broker_proto_msgTypes[20]
16631751 if x != nil {
16641752 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
16651753 if ms.LoadMessageInfo() == nil {
@@ -1672,7 +1760,7 @@ func (x *SnapshotPart) ProtoReflect() protoreflect.Message {
16721760
16731761 // Deprecated: Use SnapshotPart.ProtoReflect.Descriptor instead.
16741762 func (*SnapshotPart) Descriptor() ([]byte, []int) {
1675- return file_broker_proto_rawDescGZIP(), []int{19}
1763+ return file_broker_proto_rawDescGZIP(), []int{20}
16761764 }
16771765
16781766 func (x *SnapshotPart) GetScope() *BrokerScope {
@@ -1805,7 +1893,7 @@ type SnapshotBegin struct {
18051893
18061894 func (x *SnapshotBegin) Reset() {
18071895 *x = SnapshotBegin{}
1808- mi := &file_broker_proto_msgTypes[20]
1896+ mi := &file_broker_proto_msgTypes[21]
18091897 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
18101898 ms.StoreMessageInfo(mi)
18111899 }
@@ -1817,7 +1905,7 @@ func (x *SnapshotBegin) String() string {
18171905 func (*SnapshotBegin) ProtoMessage() {}
18181906
18191907 func (x *SnapshotBegin) ProtoReflect() protoreflect.Message {
1820- mi := &file_broker_proto_msgTypes[20]
1908+ mi := &file_broker_proto_msgTypes[21]
18211909 if x != nil {
18221910 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
18231911 if ms.LoadMessageInfo() == nil {
@@ -1830,7 +1918,7 @@ func (x *SnapshotBegin) ProtoReflect() protoreflect.Message {
18301918
18311919 // Deprecated: Use SnapshotBegin.ProtoReflect.Descriptor instead.
18321920 func (*SnapshotBegin) Descriptor() ([]byte, []int) {
1833- return file_broker_proto_rawDescGZIP(), []int{20}
1921+ return file_broker_proto_rawDescGZIP(), []int{21}
18341922 }
18351923
18361924 func (x *SnapshotBegin) GetHostCount() uint32 {
@@ -1905,7 +1993,7 @@ type SnapshotDaemon struct {
19051993
19061994 func (x *SnapshotDaemon) Reset() {
19071995 *x = SnapshotDaemon{}
1908- mi := &file_broker_proto_msgTypes[21]
1996+ mi := &file_broker_proto_msgTypes[22]
19091997 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
19101998 ms.StoreMessageInfo(mi)
19111999 }
@@ -1917,7 +2005,7 @@ func (x *SnapshotDaemon) String() string {
19172005 func (*SnapshotDaemon) ProtoMessage() {}
19182006
19192007 func (x *SnapshotDaemon) ProtoReflect() protoreflect.Message {
1920- mi := &file_broker_proto_msgTypes[21]
2008+ mi := &file_broker_proto_msgTypes[22]
19212009 if x != nil {
19222010 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
19232011 if ms.LoadMessageInfo() == nil {
@@ -1930,7 +2018,7 @@ func (x *SnapshotDaemon) ProtoReflect() protoreflect.Message {
19302018
19312019 // Deprecated: Use SnapshotDaemon.ProtoReflect.Descriptor instead.
19322020 func (*SnapshotDaemon) Descriptor() ([]byte, []int) {
1933- return file_broker_proto_rawDescGZIP(), []int{21}
2021+ return file_broker_proto_rawDescGZIP(), []int{22}
19342022 }
19352023
19362024 func (x *SnapshotDaemon) GetHostIndex() uint32 {
@@ -2095,7 +2183,7 @@ type SnapshotSession struct {
20952183
20962184 func (x *SnapshotSession) Reset() {
20972185 *x = SnapshotSession{}
2098- mi := &file_broker_proto_msgTypes[22]
2186+ mi := &file_broker_proto_msgTypes[23]
20992187 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
21002188 ms.StoreMessageInfo(mi)
21012189 }
@@ -2107,7 +2195,7 @@ func (x *SnapshotSession) String() string {
21072195 func (*SnapshotSession) ProtoMessage() {}
21082196
21092197 func (x *SnapshotSession) ProtoReflect() protoreflect.Message {
2110- mi := &file_broker_proto_msgTypes[22]
2198+ mi := &file_broker_proto_msgTypes[23]
21112199 if x != nil {
21122200 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
21132201 if ms.LoadMessageInfo() == nil {
@@ -2120,7 +2208,7 @@ func (x *SnapshotSession) ProtoReflect() protoreflect.Message {
21202208
21212209 // Deprecated: Use SnapshotSession.ProtoReflect.Descriptor instead.
21222210 func (*SnapshotSession) Descriptor() ([]byte, []int) {
2123- return file_broker_proto_rawDescGZIP(), []int{22}
2211+ return file_broker_proto_rawDescGZIP(), []int{23}
21242212 }
21252213
21262214 func (x *SnapshotSession) GetHostIndex() uint32 {
@@ -2164,7 +2252,7 @@ type SnapshotTombstone struct {
21642252
21652253 func (x *SnapshotTombstone) Reset() {
21662254 *x = SnapshotTombstone{}
2167- mi := &file_broker_proto_msgTypes[23]
2255+ mi := &file_broker_proto_msgTypes[24]
21682256 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
21692257 ms.StoreMessageInfo(mi)
21702258 }
@@ -2176,7 +2264,7 @@ func (x *SnapshotTombstone) String() string {
21762264 func (*SnapshotTombstone) ProtoMessage() {}
21772265
21782266 func (x *SnapshotTombstone) ProtoReflect() protoreflect.Message {
2179- mi := &file_broker_proto_msgTypes[23]
2267+ mi := &file_broker_proto_msgTypes[24]
21802268 if x != nil {
21812269 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
21822270 if ms.LoadMessageInfo() == nil {
@@ -2189,7 +2277,7 @@ func (x *SnapshotTombstone) ProtoReflect() protoreflect.Message {
21892277
21902278 // Deprecated: Use SnapshotTombstone.ProtoReflect.Descriptor instead.
21912279 func (*SnapshotTombstone) Descriptor() ([]byte, []int) {
2192- return file_broker_proto_rawDescGZIP(), []int{23}
2280+ return file_broker_proto_rawDescGZIP(), []int{24}
21932281 }
21942282
21952283 func (x *SnapshotTombstone) GetTombstoneIndex() uint32 {
@@ -2222,7 +2310,7 @@ type SnapshotEnd struct {
22222310
22232311 func (x *SnapshotEnd) Reset() {
22242312 *x = SnapshotEnd{}
2225- mi := &file_broker_proto_msgTypes[24]
2313+ mi := &file_broker_proto_msgTypes[25]
22262314 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
22272315 ms.StoreMessageInfo(mi)
22282316 }
@@ -2234,7 +2322,7 @@ func (x *SnapshotEnd) String() string {
22342322 func (*SnapshotEnd) ProtoMessage() {}
22352323
22362324 func (x *SnapshotEnd) ProtoReflect() protoreflect.Message {
2237- mi := &file_broker_proto_msgTypes[24]
2325+ mi := &file_broker_proto_msgTypes[25]
22382326 if x != nil {
22392327 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
22402328 if ms.LoadMessageInfo() == nil {
@@ -2247,7 +2335,7 @@ func (x *SnapshotEnd) ProtoReflect() protoreflect.Message {
22472335
22482336 // Deprecated: Use SnapshotEnd.ProtoReflect.Descriptor instead.
22492337 func (*SnapshotEnd) Descriptor() ([]byte, []int) {
2250- return file_broker_proto_rawDescGZIP(), []int{24}
2338+ return file_broker_proto_rawDescGZIP(), []int{25}
22512339 }
22522340
22532341 // OperationResult is exactly one completion for one admitted mutating
@@ -2270,7 +2358,7 @@ type OperationResult struct {
22702358
22712359 func (x *OperationResult) Reset() {
22722360 *x = OperationResult{}
2273- mi := &file_broker_proto_msgTypes[25]
2361+ mi := &file_broker_proto_msgTypes[26]
22742362 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
22752363 ms.StoreMessageInfo(mi)
22762364 }
@@ -2282,7 +2370,7 @@ func (x *OperationResult) String() string {
22822370 func (*OperationResult) ProtoMessage() {}
22832371
22842372 func (x *OperationResult) ProtoReflect() protoreflect.Message {
2285- mi := &file_broker_proto_msgTypes[25]
2373+ mi := &file_broker_proto_msgTypes[26]
22862374 if x != nil {
22872375 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
22882376 if ms.LoadMessageInfo() == nil {
@@ -2295,7 +2383,7 @@ func (x *OperationResult) ProtoReflect() protoreflect.Message {
22952383
22962384 // Deprecated: Use OperationResult.ProtoReflect.Descriptor instead.
22972385 func (*OperationResult) Descriptor() ([]byte, []int) {
2298- return file_broker_proto_rawDescGZIP(), []int{25}
2386+ return file_broker_proto_rawDescGZIP(), []int{26}
22992387 }
23002388
23012389 func (x *OperationResult) GetScope() *BrokerScope {
@@ -2350,7 +2438,7 @@ type StreamOpened struct {
23502438
23512439 func (x *StreamOpened) Reset() {
23522440 *x = StreamOpened{}
2353- mi := &file_broker_proto_msgTypes[26]
2441+ mi := &file_broker_proto_msgTypes[27]
23542442 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
23552443 ms.StoreMessageInfo(mi)
23562444 }
@@ -2362,7 +2450,7 @@ func (x *StreamOpened) String() string {
23622450 func (*StreamOpened) ProtoMessage() {}
23632451
23642452 func (x *StreamOpened) ProtoReflect() protoreflect.Message {
2365- mi := &file_broker_proto_msgTypes[26]
2453+ mi := &file_broker_proto_msgTypes[27]
23662454 if x != nil {
23672455 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
23682456 if ms.LoadMessageInfo() == nil {
@@ -2375,7 +2463,7 @@ func (x *StreamOpened) ProtoReflect() protoreflect.Message {
23752463
23762464 // Deprecated: Use StreamOpened.ProtoReflect.Descriptor instead.
23772465 func (*StreamOpened) Descriptor() ([]byte, []int) {
2378- return file_broker_proto_rawDescGZIP(), []int{26}
2466+ return file_broker_proto_rawDescGZIP(), []int{27}
23792467 }
23802468
23812469 func (x *StreamOpened) GetRef() *BrokerStreamRef {
@@ -2396,7 +2484,7 @@ type ServerStreamData struct {
23962484
23972485 func (x *ServerStreamData) Reset() {
23982486 *x = ServerStreamData{}
2399- mi := &file_broker_proto_msgTypes[27]
2487+ mi := &file_broker_proto_msgTypes[28]
24002488 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
24012489 ms.StoreMessageInfo(mi)
24022490 }
@@ -2408,7 +2496,7 @@ func (x *ServerStreamData) String() string {
24082496 func (*ServerStreamData) ProtoMessage() {}
24092497
24102498 func (x *ServerStreamData) ProtoReflect() protoreflect.Message {
2411- mi := &file_broker_proto_msgTypes[27]
2499+ mi := &file_broker_proto_msgTypes[28]
24122500 if x != nil {
24132501 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
24142502 if ms.LoadMessageInfo() == nil {
@@ -2421,7 +2509,7 @@ func (x *ServerStreamData) ProtoReflect() protoreflect.Message {
24212509
24222510 // Deprecated: Use ServerStreamData.ProtoReflect.Descriptor instead.
24232511 func (*ServerStreamData) Descriptor() ([]byte, []int) {
2424- return file_broker_proto_rawDescGZIP(), []int{27}
2512+ return file_broker_proto_rawDescGZIP(), []int{28}
24252513 }
24262514
24272515 func (x *ServerStreamData) GetRef() *BrokerStreamRef {
@@ -2450,7 +2538,7 @@ type StreamClosed struct {
24502538
24512539 func (x *StreamClosed) Reset() {
24522540 *x = StreamClosed{}
2453- mi := &file_broker_proto_msgTypes[28]
2541+ mi := &file_broker_proto_msgTypes[29]
24542542 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
24552543 ms.StoreMessageInfo(mi)
24562544 }
@@ -2462,7 +2550,7 @@ func (x *StreamClosed) String() string {
24622550 func (*StreamClosed) ProtoMessage() {}
24632551
24642552 func (x *StreamClosed) ProtoReflect() protoreflect.Message {
2465- mi := &file_broker_proto_msgTypes[28]
2553+ mi := &file_broker_proto_msgTypes[29]
24662554 if x != nil {
24672555 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
24682556 if ms.LoadMessageInfo() == nil {
@@ -2475,7 +2563,7 @@ func (x *StreamClosed) ProtoReflect() protoreflect.Message {
24752563
24762564 // Deprecated: Use StreamClosed.ProtoReflect.Descriptor instead.
24772565 func (*StreamClosed) Descriptor() ([]byte, []int) {
2478- return file_broker_proto_rawDescGZIP(), []int{28}
2566+ return file_broker_proto_rawDescGZIP(), []int{29}
24792567 }
24802568
24812569 func (x *StreamClosed) GetRef() *BrokerStreamRef {
@@ -2504,7 +2592,7 @@ type Progress struct {
25042592
25052593 func (x *Progress) Reset() {
25062594 *x = Progress{}
2507- mi := &file_broker_proto_msgTypes[29]
2595+ mi := &file_broker_proto_msgTypes[30]
25082596 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
25092597 ms.StoreMessageInfo(mi)
25102598 }
@@ -2516,7 +2604,7 @@ func (x *Progress) String() string {
25162604 func (*Progress) ProtoMessage() {}
25172605
25182606 func (x *Progress) ProtoReflect() protoreflect.Message {
2519- mi := &file_broker_proto_msgTypes[29]
2607+ mi := &file_broker_proto_msgTypes[30]
25202608 if x != nil {
25212609 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
25222610 if ms.LoadMessageInfo() == nil {
@@ -2529,7 +2617,7 @@ func (x *Progress) ProtoReflect() protoreflect.Message {
25292617
25302618 // Deprecated: Use Progress.ProtoReflect.Descriptor instead.
25312619 func (*Progress) Descriptor() ([]byte, []int) {
2532- return file_broker_proto_rawDescGZIP(), []int{29}
2620+ return file_broker_proto_rawDescGZIP(), []int{30}
25332621 }
25342622
25352623 func (x *Progress) GetRef() *BrokerStreamRef {
@@ -2564,7 +2652,7 @@ type BrokerErrorMessage struct {
25642652
25652653 func (x *BrokerErrorMessage) Reset() {
25662654 *x = BrokerErrorMessage{}
2567- mi := &file_broker_proto_msgTypes[30]
2655+ mi := &file_broker_proto_msgTypes[31]
25682656 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
25692657 ms.StoreMessageInfo(mi)
25702658 }
@@ -2576,7 +2664,7 @@ func (x *BrokerErrorMessage) String() string {
25762664 func (*BrokerErrorMessage) ProtoMessage() {}
25772665
25782666 func (x *BrokerErrorMessage) ProtoReflect() protoreflect.Message {
2579- mi := &file_broker_proto_msgTypes[30]
2667+ mi := &file_broker_proto_msgTypes[31]
25802668 if x != nil {
25812669 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
25822670 if ms.LoadMessageInfo() == nil {
@@ -2589,7 +2677,7 @@ func (x *BrokerErrorMessage) ProtoReflect() protoreflect.Message {
25892677
25902678 // Deprecated: Use BrokerErrorMessage.ProtoReflect.Descriptor instead.
25912679 func (*BrokerErrorMessage) Descriptor() ([]byte, []int) {
2592- return file_broker_proto_rawDescGZIP(), []int{30}
2680+ return file_broker_proto_rawDescGZIP(), []int{31}
25932681 }
25942682
25952683 func (x *BrokerErrorMessage) GetScope() *BrokerScope {
@@ -2619,7 +2707,7 @@ type Shutdown struct {
26192707
26202708 func (x *Shutdown) Reset() {
26212709 *x = Shutdown{}
2622- mi := &file_broker_proto_msgTypes[31]
2710+ mi := &file_broker_proto_msgTypes[32]
26232711 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
26242712 ms.StoreMessageInfo(mi)
26252713 }
@@ -2631,7 +2719,7 @@ func (x *Shutdown) String() string {
26312719 func (*Shutdown) ProtoMessage() {}
26322720
26332721 func (x *Shutdown) ProtoReflect() protoreflect.Message {
2634- mi := &file_broker_proto_msgTypes[31]
2722+ mi := &file_broker_proto_msgTypes[32]
26352723 if x != nil {
26362724 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
26372725 if ms.LoadMessageInfo() == nil {
@@ -2644,7 +2732,7 @@ func (x *Shutdown) ProtoReflect() protoreflect.Message {
26442732
26452733 // Deprecated: Use Shutdown.ProtoReflect.Descriptor instead.
26462734 func (*Shutdown) Descriptor() ([]byte, []int) {
2647- return file_broker_proto_rawDescGZIP(), []int{31}
2735+ return file_broker_proto_rawDescGZIP(), []int{32}
26482736 }
26492737
26502738 func (x *Shutdown) GetScope() *BrokerScope {
@@ -2683,7 +2771,7 @@ type PreviewRoute struct {
26832771
26842772 func (x *PreviewRoute) Reset() {
26852773 *x = PreviewRoute{}
2686- mi := &file_broker_proto_msgTypes[32]
2774+ mi := &file_broker_proto_msgTypes[33]
26872775 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
26882776 ms.StoreMessageInfo(mi)
26892777 }
@@ -2695,7 +2783,7 @@ func (x *PreviewRoute) String() string {
26952783 func (*PreviewRoute) ProtoMessage() {}
26962784
26972785 func (x *PreviewRoute) ProtoReflect() protoreflect.Message {
2698- mi := &file_broker_proto_msgTypes[32]
2786+ mi := &file_broker_proto_msgTypes[33]
26992787 if x != nil {
27002788 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
27012789 if ms.LoadMessageInfo() == nil {
@@ -2708,7 +2796,7 @@ func (x *PreviewRoute) ProtoReflect() protoreflect.Message {
27082796
27092797 // Deprecated: Use PreviewRoute.ProtoReflect.Descriptor instead.
27102798 func (*PreviewRoute) Descriptor() ([]byte, []int) {
2711- return file_broker_proto_rawDescGZIP(), []int{32}
2799+ return file_broker_proto_rawDescGZIP(), []int{33}
27122800 }
27132801
27142802 func (x *PreviewRoute) GetLocal() bool {
@@ -2756,7 +2844,7 @@ type StartPreview struct {
27562844
27572845 func (x *StartPreview) Reset() {
27582846 *x = StartPreview{}
2759- mi := &file_broker_proto_msgTypes[33]
2847+ mi := &file_broker_proto_msgTypes[34]
27602848 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
27612849 ms.StoreMessageInfo(mi)
27622850 }
@@ -2768,7 +2856,7 @@ func (x *StartPreview) String() string {
27682856 func (*StartPreview) ProtoMessage() {}
27692857
27702858 func (x *StartPreview) ProtoReflect() protoreflect.Message {
2771- mi := &file_broker_proto_msgTypes[33]
2859+ mi := &file_broker_proto_msgTypes[34]
27722860 if x != nil {
27732861 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
27742862 if ms.LoadMessageInfo() == nil {
@@ -2781,7 +2869,7 @@ func (x *StartPreview) ProtoReflect() protoreflect.Message {
27812869
27822870 // Deprecated: Use StartPreview.ProtoReflect.Descriptor instead.
27832871 func (*StartPreview) Descriptor() ([]byte, []int) {
2784- return file_broker_proto_rawDescGZIP(), []int{33}
2872+ return file_broker_proto_rawDescGZIP(), []int{34}
27852873 }
27862874
27872875 func (x *StartPreview) GetScope() *BrokerScope {
@@ -2823,7 +2911,7 @@ type CancelPreview struct {
28232911
28242912 func (x *CancelPreview) Reset() {
28252913 *x = CancelPreview{}
2826- mi := &file_broker_proto_msgTypes[34]
2914+ mi := &file_broker_proto_msgTypes[35]
28272915 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
28282916 ms.StoreMessageInfo(mi)
28292917 }
@@ -2835,7 +2923,7 @@ func (x *CancelPreview) String() string {
28352923 func (*CancelPreview) ProtoMessage() {}
28362924
28372925 func (x *CancelPreview) ProtoReflect() protoreflect.Message {
2838- mi := &file_broker_proto_msgTypes[34]
2926+ mi := &file_broker_proto_msgTypes[35]
28392927 if x != nil {
28402928 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
28412929 if ms.LoadMessageInfo() == nil {
@@ -2848,7 +2936,7 @@ func (x *CancelPreview) ProtoReflect() protoreflect.Message {
28482936
28492937 // Deprecated: Use CancelPreview.ProtoReflect.Descriptor instead.
28502938 func (*CancelPreview) Descriptor() ([]byte, []int) {
2851- return file_broker_proto_rawDescGZIP(), []int{34}
2939+ return file_broker_proto_rawDescGZIP(), []int{35}
28522940 }
28532941
28542942 func (x *CancelPreview) GetScope() *BrokerScope {
@@ -2882,7 +2970,7 @@ type PreviewPublication struct {
28822970
28832971 func (x *PreviewPublication) Reset() {
28842972 *x = PreviewPublication{}
2885- mi := &file_broker_proto_msgTypes[35]
2973+ mi := &file_broker_proto_msgTypes[36]
28862974 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
28872975 ms.StoreMessageInfo(mi)
28882976 }
@@ -2894,7 +2982,7 @@ func (x *PreviewPublication) String() string {
28942982 func (*PreviewPublication) ProtoMessage() {}
28952983
28962984 func (x *PreviewPublication) ProtoReflect() protoreflect.Message {
2897- mi := &file_broker_proto_msgTypes[35]
2985+ mi := &file_broker_proto_msgTypes[36]
28982986 if x != nil {
28992987 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
29002988 if ms.LoadMessageInfo() == nil {
@@ -2907,7 +2995,7 @@ func (x *PreviewPublication) ProtoReflect() protoreflect.Message {
29072995
29082996 // Deprecated: Use PreviewPublication.ProtoReflect.Descriptor instead.
29092997 func (*PreviewPublication) Descriptor() ([]byte, []int) {
2910- return file_broker_proto_rawDescGZIP(), []int{35}
2998+ return file_broker_proto_rawDescGZIP(), []int{36}
29112999 }
29123000
29133001 func (x *PreviewPublication) GetScope() *BrokerScope {
@@ -2987,7 +3075,7 @@ type BrokerCatalogSession struct {
29873075
29883076 func (x *BrokerCatalogSession) Reset() {
29893077 *x = BrokerCatalogSession{}
2990- mi := &file_broker_proto_msgTypes[36]
3078+ mi := &file_broker_proto_msgTypes[37]
29913079 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
29923080 ms.StoreMessageInfo(mi)
29933081 }
@@ -2999,7 +3087,7 @@ func (x *BrokerCatalogSession) String() string {
29993087 func (*BrokerCatalogSession) ProtoMessage() {}
30003088
30013089 func (x *BrokerCatalogSession) ProtoReflect() protoreflect.Message {
3002- mi := &file_broker_proto_msgTypes[36]
3090+ mi := &file_broker_proto_msgTypes[37]
30033091 if x != nil {
30043092 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
30053093 if ms.LoadMessageInfo() == nil {
@@ -3012,7 +3100,7 @@ func (x *BrokerCatalogSession) ProtoReflect() protoreflect.Message {
30123100
30133101 // Deprecated: Use BrokerCatalogSession.ProtoReflect.Descriptor instead.
30143102 func (*BrokerCatalogSession) Descriptor() ([]byte, []int) {
3015- return file_broker_proto_rawDescGZIP(), []int{36}
3103+ return file_broker_proto_rawDescGZIP(), []int{37}
30163104 }
30173105
30183106 func (x *BrokerCatalogSession) GetLifecycleId() []byte {
@@ -3092,7 +3180,7 @@ type BrokerCatalogTab struct {
30923180
30933181 func (x *BrokerCatalogTab) Reset() {
30943182 *x = BrokerCatalogTab{}
3095- mi := &file_broker_proto_msgTypes[37]
3183+ mi := &file_broker_proto_msgTypes[38]
30963184 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
30973185 ms.StoreMessageInfo(mi)
30983186 }
@@ -3104,7 +3192,7 @@ func (x *BrokerCatalogTab) String() string {
31043192 func (*BrokerCatalogTab) ProtoMessage() {}
31053193
31063194 func (x *BrokerCatalogTab) ProtoReflect() protoreflect.Message {
3107- mi := &file_broker_proto_msgTypes[37]
3195+ mi := &file_broker_proto_msgTypes[38]
31083196 if x != nil {
31093197 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
31103198 if ms.LoadMessageInfo() == nil {
@@ -3117,7 +3205,7 @@ func (x *BrokerCatalogTab) ProtoReflect() protoreflect.Message {
31173205
31183206 // Deprecated: Use BrokerCatalogTab.ProtoReflect.Descriptor instead.
31193207 func (*BrokerCatalogTab) Descriptor() ([]byte, []int) {
3120- return file_broker_proto_rawDescGZIP(), []int{37}
3208+ return file_broker_proto_rawDescGZIP(), []int{38}
31213209 }
31223210
31233211 func (x *BrokerCatalogTab) GetId() string {
@@ -3181,7 +3269,7 @@ const file_broker_proto_rawDesc = "" +
31813269 "\ffailure_kind\x18\x04 \x01(\rR\vfailureKind\"A\n" +
31823270 "\x0fBrokerTimestamp\x12\x18\n" +
31833271 "\aseconds\x18\x01 \x01(\x03R\aseconds\x12\x14\n" +
3184- "\x05nanos\x18\x02 \x01(\x05R\x05nanos\"\xc2\x06\n" +
3272+ "\x05nanos\x18\x02 \x01(\x05R\x05nanos\"\x97\a\n" +
31853273 "\x14BrokerClientEnvelope\x123\n" +
31863274 "\bregister\x18e \x01(\v2\x15.vev.wire.v1.RegisterH\x00R\bregister\x126\n" +
31873275 "\tsubscribe\x18f \x01(\v2\x16.vev.wire.v1.SubscribeH\x00R\tsubscribe\x12-\n" +
@@ -3197,8 +3285,9 @@ const file_broker_proto_rawDesc = "" +
31973285 "\fclose_stream\x18n \x01(\v2\x18.vev.wire.v1.CloseStreamH\x00R\vcloseStream\x12M\n" +
31983286 "\x12update_host_policy\x18o \x01(\v2\x1d.vev.wire.v1.UpdateHostPolicyH\x00R\x10updateHostPolicy\x12@\n" +
31993287 "\rstart_preview\x18p \x01(\v2\x19.vev.wire.v1.StartPreviewH\x00R\fstartPreview\x12C\n" +
3200- "\x0ecancel_preview\x18q \x01(\v2\x1a.vev.wire.v1.CancelPreviewH\x00R\rcancelPreviewB\t\n" +
3201- "\apayload\"\xd9\x05\n" +
3288+ "\x0ecancel_preview\x18q \x01(\v2\x1a.vev.wire.v1.CancelPreviewH\x00R\rcancelPreview\x12S\n" +
3289+ "\x14stream_window_update\x18r \x01(\v2\x1f.vev.wire.v1.StreamWindowUpdateH\x00R\x12streamWindowUpdateB\t\n" +
3290+ "\apayload\"\xaf\x06\n" +
32023291 "\x14BrokerServerEnvelope\x12:\n" +
32033292 "\n" +
32043293 "registered\x18\xc9\x01 \x01(\v2\x17.vev.wire.v1.RegisteredH\x00R\n" +
@@ -3211,7 +3300,8 @@ const file_broker_proto_rawDesc = "" +
32113300 "\bprogress\x18\xcf\x01 \x01(\v2\x15.vev.wire.v1.ProgressH\x00R\bprogress\x12T\n" +
32123301 "\x14broker_error_message\x18\xd0\x01 \x01(\v2\x1f.vev.wire.v1.BrokerErrorMessageH\x00R\x12brokerErrorMessage\x124\n" +
32133302 "\bshutdown\x18\xd1\x01 \x01(\v2\x15.vev.wire.v1.ShutdownH\x00R\bshutdown\x12S\n" +
3214- "\x13preview_publication\x18\xd2\x01 \x01(\v2\x1f.vev.wire.v1.PreviewPublicationH\x00R\x12previewPublicationB\t\n" +
3303+ "\x13preview_publication\x18\xd2\x01 \x01(\v2\x1f.vev.wire.v1.PreviewPublicationH\x00R\x12previewPublication\x12T\n" +
3304+ "\x14stream_window_update\x18\xd3\x01 \x01(\v2\x1f.vev.wire.v1.StreamWindowUpdateH\x00R\x12streamWindowUpdateB\t\n" +
32153305 "\apayload\" \n" +
32163306 "\bRegister\x12\x14\n" +
32173307 "\x05build\x18\x01 \x01(\tR\x05build\"u\n" +
@@ -3268,7 +3358,10 @@ const file_broker_proto_rawDesc = "" +
32683358 "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\x12\x12\n" +
32693359 "\x04data\x18\x02 \x01(\fR\x04data\"=\n" +
32703360 "\vCloseStream\x12.\n" +
3271- "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\"n\n" +
3361+ "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\"\\\n" +
3362+ "\x12StreamWindowUpdate\x12.\n" +
3363+ "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\x12\x16\n" +
3364+ "\x06credit\x18\x02 \x01(\x04R\x06credit\"n\n" +
32723365 "\n" +
32733366 "Registered\x12.\n" +
32743367 "\x05scope\x18\x01 \x01(\v2\x18.vev.wire.v1.BrokerScopeR\x05scope\x12\x14\n" +
@@ -3409,7 +3502,7 @@ func file_broker_proto_rawDescGZIP() []byte {
34093502 return file_broker_proto_rawDescData
34103503 }
34113504
3412-var file_broker_proto_msgTypes = make([]protoimpl.MessageInfo, 38)
3505+var file_broker_proto_msgTypes = make([]protoimpl.MessageInfo, 39)
34133506 var file_broker_proto_goTypes = []any{
34143507 (*BrokerScope)(nil), // 0: vev.wire.v1.BrokerScope
34153508 (*BrokerStreamRef)(nil), // 1: vev.wire.v1.BrokerStreamRef
@@ -3429,30 +3522,31 @@ var file_broker_proto_goTypes = []any{
34293522 (*OpenStream)(nil), // 15: vev.wire.v1.OpenStream
34303523 (*ClientStreamData)(nil), // 16: vev.wire.v1.ClientStreamData
34313524 (*CloseStream)(nil), // 17: vev.wire.v1.CloseStream
3432- (*Registered)(nil), // 18: vev.wire.v1.Registered
3433- (*SnapshotPart)(nil), // 19: vev.wire.v1.SnapshotPart
3434- (*SnapshotBegin)(nil), // 20: vev.wire.v1.SnapshotBegin
3435- (*SnapshotDaemon)(nil), // 21: vev.wire.v1.SnapshotDaemon
3436- (*SnapshotSession)(nil), // 22: vev.wire.v1.SnapshotSession
3437- (*SnapshotTombstone)(nil), // 23: vev.wire.v1.SnapshotTombstone
3438- (*SnapshotEnd)(nil), // 24: vev.wire.v1.SnapshotEnd
3439- (*OperationResult)(nil), // 25: vev.wire.v1.OperationResult
3440- (*StreamOpened)(nil), // 26: vev.wire.v1.StreamOpened
3441- (*ServerStreamData)(nil), // 27: vev.wire.v1.ServerStreamData
3442- (*StreamClosed)(nil), // 28: vev.wire.v1.StreamClosed
3443- (*Progress)(nil), // 29: vev.wire.v1.Progress
3444- (*BrokerErrorMessage)(nil), // 30: vev.wire.v1.BrokerErrorMessage
3445- (*Shutdown)(nil), // 31: vev.wire.v1.Shutdown
3446- (*PreviewRoute)(nil), // 32: vev.wire.v1.PreviewRoute
3447- (*StartPreview)(nil), // 33: vev.wire.v1.StartPreview
3448- (*CancelPreview)(nil), // 34: vev.wire.v1.CancelPreview
3449- (*PreviewPublication)(nil), // 35: vev.wire.v1.PreviewPublication
3450- (*BrokerCatalogSession)(nil), // 36: vev.wire.v1.BrokerCatalogSession
3451- (*BrokerCatalogTab)(nil), // 37: vev.wire.v1.BrokerCatalogTab
3452- (*RemoteRegistration)(nil), // 38: vev.wire.v1.RemoteRegistration
3453- (*ExactTarget)(nil), // 39: vev.wire.v1.ExactTarget
3454- (*RemotePreviewRequest)(nil), // 40: vev.wire.v1.RemotePreviewRequest
3455- (*RemotePreview)(nil), // 41: vev.wire.v1.RemotePreview
3525+ (*StreamWindowUpdate)(nil), // 18: vev.wire.v1.StreamWindowUpdate
3526+ (*Registered)(nil), // 19: vev.wire.v1.Registered
3527+ (*SnapshotPart)(nil), // 20: vev.wire.v1.SnapshotPart
3528+ (*SnapshotBegin)(nil), // 21: vev.wire.v1.SnapshotBegin
3529+ (*SnapshotDaemon)(nil), // 22: vev.wire.v1.SnapshotDaemon
3530+ (*SnapshotSession)(nil), // 23: vev.wire.v1.SnapshotSession
3531+ (*SnapshotTombstone)(nil), // 24: vev.wire.v1.SnapshotTombstone
3532+ (*SnapshotEnd)(nil), // 25: vev.wire.v1.SnapshotEnd
3533+ (*OperationResult)(nil), // 26: vev.wire.v1.OperationResult
3534+ (*StreamOpened)(nil), // 27: vev.wire.v1.StreamOpened
3535+ (*ServerStreamData)(nil), // 28: vev.wire.v1.ServerStreamData
3536+ (*StreamClosed)(nil), // 29: vev.wire.v1.StreamClosed
3537+ (*Progress)(nil), // 30: vev.wire.v1.Progress
3538+ (*BrokerErrorMessage)(nil), // 31: vev.wire.v1.BrokerErrorMessage
3539+ (*Shutdown)(nil), // 32: vev.wire.v1.Shutdown
3540+ (*PreviewRoute)(nil), // 33: vev.wire.v1.PreviewRoute
3541+ (*StartPreview)(nil), // 34: vev.wire.v1.StartPreview
3542+ (*CancelPreview)(nil), // 35: vev.wire.v1.CancelPreview
3543+ (*PreviewPublication)(nil), // 36: vev.wire.v1.PreviewPublication
3544+ (*BrokerCatalogSession)(nil), // 37: vev.wire.v1.BrokerCatalogSession
3545+ (*BrokerCatalogTab)(nil), // 38: vev.wire.v1.BrokerCatalogTab
3546+ (*RemoteRegistration)(nil), // 39: vev.wire.v1.RemoteRegistration
3547+ (*ExactTarget)(nil), // 40: vev.wire.v1.ExactTarget
3548+ (*RemotePreviewRequest)(nil), // 41: vev.wire.v1.RemotePreviewRequest
3549+ (*RemotePreview)(nil), // 42: vev.wire.v1.RemotePreview
34563550 }
34573551 var file_broker_proto_depIdxs = []int32{
34583552 0, // 0: vev.wire.v1.BrokerStreamRef.scope:type_name -> vev.wire.v1.BrokerScope
@@ -3467,76 +3561,79 @@ var file_broker_proto_depIdxs = []int32{
34673561 16, // 9: vev.wire.v1.BrokerClientEnvelope.client_stream_data:type_name -> vev.wire.v1.ClientStreamData
34683562 17, // 10: vev.wire.v1.BrokerClientEnvelope.close_stream:type_name -> vev.wire.v1.CloseStream
34693563 13, // 11: vev.wire.v1.BrokerClientEnvelope.update_host_policy:type_name -> vev.wire.v1.UpdateHostPolicy
3470- 33, // 12: vev.wire.v1.BrokerClientEnvelope.start_preview:type_name -> vev.wire.v1.StartPreview
3471- 34, // 13: vev.wire.v1.BrokerClientEnvelope.cancel_preview:type_name -> vev.wire.v1.CancelPreview
3472- 18, // 14: vev.wire.v1.BrokerServerEnvelope.registered:type_name -> vev.wire.v1.Registered
3473- 19, // 15: vev.wire.v1.BrokerServerEnvelope.snapshot_part:type_name -> vev.wire.v1.SnapshotPart
3474- 25, // 16: vev.wire.v1.BrokerServerEnvelope.operation_result:type_name -> vev.wire.v1.OperationResult
3475- 26, // 17: vev.wire.v1.BrokerServerEnvelope.stream_opened:type_name -> vev.wire.v1.StreamOpened
3476- 27, // 18: vev.wire.v1.BrokerServerEnvelope.server_stream_data:type_name -> vev.wire.v1.ServerStreamData
3477- 28, // 19: vev.wire.v1.BrokerServerEnvelope.stream_closed:type_name -> vev.wire.v1.StreamClosed
3478- 29, // 20: vev.wire.v1.BrokerServerEnvelope.progress:type_name -> vev.wire.v1.Progress
3479- 30, // 21: vev.wire.v1.BrokerServerEnvelope.broker_error_message:type_name -> vev.wire.v1.BrokerErrorMessage
3480- 31, // 22: vev.wire.v1.BrokerServerEnvelope.shutdown:type_name -> vev.wire.v1.Shutdown
3481- 35, // 23: vev.wire.v1.BrokerServerEnvelope.preview_publication:type_name -> vev.wire.v1.PreviewPublication
3482- 0, // 24: vev.wire.v1.Subscribe.scope:type_name -> vev.wire.v1.BrokerScope
3483- 0, // 25: vev.wire.v1.Resync.scope:type_name -> vev.wire.v1.BrokerScope
3484- 0, // 26: vev.wire.v1.Unsubscribe.scope:type_name -> vev.wire.v1.BrokerScope
3485- 0, // 27: vev.wire.v1.AddHost.scope:type_name -> vev.wire.v1.BrokerScope
3486- 2, // 28: vev.wire.v1.AddHost.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3487- 0, // 29: vev.wire.v1.RemoveHost.scope:type_name -> vev.wire.v1.BrokerScope
3488- 38, // 30: vev.wire.v1.RemoveHost.registration:type_name -> vev.wire.v1.RemoteRegistration
3489- 0, // 31: vev.wire.v1.UpdateHostPolicy.scope:type_name -> vev.wire.v1.BrokerScope
3490- 38, // 32: vev.wire.v1.UpdateHostPolicy.registration:type_name -> vev.wire.v1.RemoteRegistration
3491- 2, // 33: vev.wire.v1.UpdateHostPolicy.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3492- 0, // 34: vev.wire.v1.Reconcile.scope:type_name -> vev.wire.v1.BrokerScope
3493- 38, // 35: vev.wire.v1.Reconcile.registration:type_name -> vev.wire.v1.RemoteRegistration
3494- 1, // 36: vev.wire.v1.OpenStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3495- 38, // 37: vev.wire.v1.OpenStream.registration:type_name -> vev.wire.v1.RemoteRegistration
3496- 39, // 38: vev.wire.v1.OpenStream.target:type_name -> vev.wire.v1.ExactTarget
3497- 2, // 39: vev.wire.v1.OpenStream.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3498- 1, // 40: vev.wire.v1.ClientStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3499- 1, // 41: vev.wire.v1.CloseStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3500- 0, // 42: vev.wire.v1.Registered.scope:type_name -> vev.wire.v1.BrokerScope
3501- 0, // 43: vev.wire.v1.SnapshotPart.scope:type_name -> vev.wire.v1.BrokerScope
3502- 20, // 44: vev.wire.v1.SnapshotPart.begin:type_name -> vev.wire.v1.SnapshotBegin
3503- 21, // 45: vev.wire.v1.SnapshotPart.daemon:type_name -> vev.wire.v1.SnapshotDaemon
3504- 22, // 46: vev.wire.v1.SnapshotPart.session:type_name -> vev.wire.v1.SnapshotSession
3505- 23, // 47: vev.wire.v1.SnapshotPart.tombstone:type_name -> vev.wire.v1.SnapshotTombstone
3506- 24, // 48: vev.wire.v1.SnapshotPart.end:type_name -> vev.wire.v1.SnapshotEnd
3507- 38, // 49: vev.wire.v1.SnapshotDaemon.registration:type_name -> vev.wire.v1.RemoteRegistration
3508- 2, // 50: vev.wire.v1.SnapshotDaemon.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3509- 4, // 51: vev.wire.v1.SnapshotDaemon.last_attempt:type_name -> vev.wire.v1.BrokerTimestamp
3510- 4, // 52: vev.wire.v1.SnapshotDaemon.last_success:type_name -> vev.wire.v1.BrokerTimestamp
3511- 4, // 53: vev.wire.v1.SnapshotDaemon.next_due:type_name -> vev.wire.v1.BrokerTimestamp
3512- 36, // 54: vev.wire.v1.SnapshotSession.session:type_name -> vev.wire.v1.BrokerCatalogSession
3513- 38, // 55: vev.wire.v1.SnapshotTombstone.registration:type_name -> vev.wire.v1.RemoteRegistration
3514- 0, // 56: vev.wire.v1.OperationResult.scope:type_name -> vev.wire.v1.BrokerScope
3515- 3, // 57: vev.wire.v1.OperationResult.error:type_name -> vev.wire.v1.BrokerErrorDetail
3516- 38, // 58: vev.wire.v1.OperationResult.registration:type_name -> vev.wire.v1.RemoteRegistration
3517- 1, // 59: vev.wire.v1.StreamOpened.ref:type_name -> vev.wire.v1.BrokerStreamRef
3518- 1, // 60: vev.wire.v1.ServerStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3519- 1, // 61: vev.wire.v1.StreamClosed.ref:type_name -> vev.wire.v1.BrokerStreamRef
3520- 3, // 62: vev.wire.v1.StreamClosed.error:type_name -> vev.wire.v1.BrokerErrorDetail
3521- 1, // 63: vev.wire.v1.Progress.ref:type_name -> vev.wire.v1.BrokerStreamRef
3522- 0, // 64: vev.wire.v1.BrokerErrorMessage.scope:type_name -> vev.wire.v1.BrokerScope
3523- 3, // 65: vev.wire.v1.BrokerErrorMessage.error:type_name -> vev.wire.v1.BrokerErrorDetail
3524- 0, // 66: vev.wire.v1.Shutdown.scope:type_name -> vev.wire.v1.BrokerScope
3525- 38, // 67: vev.wire.v1.PreviewRoute.registration:type_name -> vev.wire.v1.RemoteRegistration
3526- 2, // 68: vev.wire.v1.PreviewRoute.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3527- 0, // 69: vev.wire.v1.StartPreview.scope:type_name -> vev.wire.v1.BrokerScope
3528- 40, // 70: vev.wire.v1.StartPreview.preview:type_name -> vev.wire.v1.RemotePreviewRequest
3529- 32, // 71: vev.wire.v1.StartPreview.route:type_name -> vev.wire.v1.PreviewRoute
3530- 0, // 72: vev.wire.v1.CancelPreview.scope:type_name -> vev.wire.v1.BrokerScope
3531- 0, // 73: vev.wire.v1.PreviewPublication.scope:type_name -> vev.wire.v1.BrokerScope
3532- 41, // 74: vev.wire.v1.PreviewPublication.preview:type_name -> vev.wire.v1.RemotePreview
3533- 3, // 75: vev.wire.v1.PreviewPublication.error:type_name -> vev.wire.v1.BrokerErrorDetail
3534- 37, // 76: vev.wire.v1.BrokerCatalogSession.tabs:type_name -> vev.wire.v1.BrokerCatalogTab
3535- 77, // [77:77] is the sub-list for method output_type
3536- 77, // [77:77] is the sub-list for method input_type
3537- 77, // [77:77] is the sub-list for extension type_name
3538- 77, // [77:77] is the sub-list for extension extendee
3539- 0, // [0:77] is the sub-list for field type_name
3564+ 34, // 12: vev.wire.v1.BrokerClientEnvelope.start_preview:type_name -> vev.wire.v1.StartPreview
3565+ 35, // 13: vev.wire.v1.BrokerClientEnvelope.cancel_preview:type_name -> vev.wire.v1.CancelPreview
3566+ 18, // 14: vev.wire.v1.BrokerClientEnvelope.stream_window_update:type_name -> vev.wire.v1.StreamWindowUpdate
3567+ 19, // 15: vev.wire.v1.BrokerServerEnvelope.registered:type_name -> vev.wire.v1.Registered
3568+ 20, // 16: vev.wire.v1.BrokerServerEnvelope.snapshot_part:type_name -> vev.wire.v1.SnapshotPart
3569+ 26, // 17: vev.wire.v1.BrokerServerEnvelope.operation_result:type_name -> vev.wire.v1.OperationResult
3570+ 27, // 18: vev.wire.v1.BrokerServerEnvelope.stream_opened:type_name -> vev.wire.v1.StreamOpened
3571+ 28, // 19: vev.wire.v1.BrokerServerEnvelope.server_stream_data:type_name -> vev.wire.v1.ServerStreamData
3572+ 29, // 20: vev.wire.v1.BrokerServerEnvelope.stream_closed:type_name -> vev.wire.v1.StreamClosed
3573+ 30, // 21: vev.wire.v1.BrokerServerEnvelope.progress:type_name -> vev.wire.v1.Progress
3574+ 31, // 22: vev.wire.v1.BrokerServerEnvelope.broker_error_message:type_name -> vev.wire.v1.BrokerErrorMessage
3575+ 32, // 23: vev.wire.v1.BrokerServerEnvelope.shutdown:type_name -> vev.wire.v1.Shutdown
3576+ 36, // 24: vev.wire.v1.BrokerServerEnvelope.preview_publication:type_name -> vev.wire.v1.PreviewPublication
3577+ 18, // 25: vev.wire.v1.BrokerServerEnvelope.stream_window_update:type_name -> vev.wire.v1.StreamWindowUpdate
3578+ 0, // 26: vev.wire.v1.Subscribe.scope:type_name -> vev.wire.v1.BrokerScope
3579+ 0, // 27: vev.wire.v1.Resync.scope:type_name -> vev.wire.v1.BrokerScope
3580+ 0, // 28: vev.wire.v1.Unsubscribe.scope:type_name -> vev.wire.v1.BrokerScope
3581+ 0, // 29: vev.wire.v1.AddHost.scope:type_name -> vev.wire.v1.BrokerScope
3582+ 2, // 30: vev.wire.v1.AddHost.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3583+ 0, // 31: vev.wire.v1.RemoveHost.scope:type_name -> vev.wire.v1.BrokerScope
3584+ 39, // 32: vev.wire.v1.RemoveHost.registration:type_name -> vev.wire.v1.RemoteRegistration
3585+ 0, // 33: vev.wire.v1.UpdateHostPolicy.scope:type_name -> vev.wire.v1.BrokerScope
3586+ 39, // 34: vev.wire.v1.UpdateHostPolicy.registration:type_name -> vev.wire.v1.RemoteRegistration
3587+ 2, // 35: vev.wire.v1.UpdateHostPolicy.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3588+ 0, // 36: vev.wire.v1.Reconcile.scope:type_name -> vev.wire.v1.BrokerScope
3589+ 39, // 37: vev.wire.v1.Reconcile.registration:type_name -> vev.wire.v1.RemoteRegistration
3590+ 1, // 38: vev.wire.v1.OpenStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3591+ 39, // 39: vev.wire.v1.OpenStream.registration:type_name -> vev.wire.v1.RemoteRegistration
3592+ 40, // 40: vev.wire.v1.OpenStream.target:type_name -> vev.wire.v1.ExactTarget
3593+ 2, // 41: vev.wire.v1.OpenStream.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3594+ 1, // 42: vev.wire.v1.ClientStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3595+ 1, // 43: vev.wire.v1.CloseStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3596+ 1, // 44: vev.wire.v1.StreamWindowUpdate.ref:type_name -> vev.wire.v1.BrokerStreamRef
3597+ 0, // 45: vev.wire.v1.Registered.scope:type_name -> vev.wire.v1.BrokerScope
3598+ 0, // 46: vev.wire.v1.SnapshotPart.scope:type_name -> vev.wire.v1.BrokerScope
3599+ 21, // 47: vev.wire.v1.SnapshotPart.begin:type_name -> vev.wire.v1.SnapshotBegin
3600+ 22, // 48: vev.wire.v1.SnapshotPart.daemon:type_name -> vev.wire.v1.SnapshotDaemon
3601+ 23, // 49: vev.wire.v1.SnapshotPart.session:type_name -> vev.wire.v1.SnapshotSession
3602+ 24, // 50: vev.wire.v1.SnapshotPart.tombstone:type_name -> vev.wire.v1.SnapshotTombstone
3603+ 25, // 51: vev.wire.v1.SnapshotPart.end:type_name -> vev.wire.v1.SnapshotEnd
3604+ 39, // 52: vev.wire.v1.SnapshotDaemon.registration:type_name -> vev.wire.v1.RemoteRegistration
3605+ 2, // 53: vev.wire.v1.SnapshotDaemon.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3606+ 4, // 54: vev.wire.v1.SnapshotDaemon.last_attempt:type_name -> vev.wire.v1.BrokerTimestamp
3607+ 4, // 55: vev.wire.v1.SnapshotDaemon.last_success:type_name -> vev.wire.v1.BrokerTimestamp
3608+ 4, // 56: vev.wire.v1.SnapshotDaemon.next_due:type_name -> vev.wire.v1.BrokerTimestamp
3609+ 37, // 57: vev.wire.v1.SnapshotSession.session:type_name -> vev.wire.v1.BrokerCatalogSession
3610+ 39, // 58: vev.wire.v1.SnapshotTombstone.registration:type_name -> vev.wire.v1.RemoteRegistration
3611+ 0, // 59: vev.wire.v1.OperationResult.scope:type_name -> vev.wire.v1.BrokerScope
3612+ 3, // 60: vev.wire.v1.OperationResult.error:type_name -> vev.wire.v1.BrokerErrorDetail
3613+ 39, // 61: vev.wire.v1.OperationResult.registration:type_name -> vev.wire.v1.RemoteRegistration
3614+ 1, // 62: vev.wire.v1.StreamOpened.ref:type_name -> vev.wire.v1.BrokerStreamRef
3615+ 1, // 63: vev.wire.v1.ServerStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3616+ 1, // 64: vev.wire.v1.StreamClosed.ref:type_name -> vev.wire.v1.BrokerStreamRef
3617+ 3, // 65: vev.wire.v1.StreamClosed.error:type_name -> vev.wire.v1.BrokerErrorDetail
3618+ 1, // 66: vev.wire.v1.Progress.ref:type_name -> vev.wire.v1.BrokerStreamRef
3619+ 0, // 67: vev.wire.v1.BrokerErrorMessage.scope:type_name -> vev.wire.v1.BrokerScope
3620+ 3, // 68: vev.wire.v1.BrokerErrorMessage.error:type_name -> vev.wire.v1.BrokerErrorDetail
3621+ 0, // 69: vev.wire.v1.Shutdown.scope:type_name -> vev.wire.v1.BrokerScope
3622+ 39, // 70: vev.wire.v1.PreviewRoute.registration:type_name -> vev.wire.v1.RemoteRegistration
3623+ 2, // 71: vev.wire.v1.PreviewRoute.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3624+ 0, // 72: vev.wire.v1.StartPreview.scope:type_name -> vev.wire.v1.BrokerScope
3625+ 41, // 73: vev.wire.v1.StartPreview.preview:type_name -> vev.wire.v1.RemotePreviewRequest
3626+ 33, // 74: vev.wire.v1.StartPreview.route:type_name -> vev.wire.v1.PreviewRoute
3627+ 0, // 75: vev.wire.v1.CancelPreview.scope:type_name -> vev.wire.v1.BrokerScope
3628+ 0, // 76: vev.wire.v1.PreviewPublication.scope:type_name -> vev.wire.v1.BrokerScope
3629+ 42, // 77: vev.wire.v1.PreviewPublication.preview:type_name -> vev.wire.v1.RemotePreview
3630+ 3, // 78: vev.wire.v1.PreviewPublication.error:type_name -> vev.wire.v1.BrokerErrorDetail
3631+ 38, // 79: vev.wire.v1.BrokerCatalogSession.tabs:type_name -> vev.wire.v1.BrokerCatalogTab
3632+ 80, // [80:80] is the sub-list for method output_type
3633+ 80, // [80:80] is the sub-list for method input_type
3634+ 80, // [80:80] is the sub-list for extension type_name
3635+ 80, // [80:80] is the sub-list for extension extendee
3636+ 0, // [0:80] is the sub-list for field type_name
35403637 }
35413638
35423639 func init() { file_broker_proto_init() }
@@ -3560,6 +3657,7 @@ func file_broker_proto_init() {
35603657 (*BrokerClientEnvelope_UpdateHostPolicy)(nil),
35613658 (*BrokerClientEnvelope_StartPreview)(nil),
35623659 (*BrokerClientEnvelope_CancelPreview)(nil),
3660+ (*BrokerClientEnvelope_StreamWindowUpdate)(nil),
35633661 }
35643662 file_broker_proto_msgTypes[6].OneofWrappers = []any{
35653663 (*BrokerServerEnvelope_Registered)(nil),
@@ -3572,15 +3670,16 @@ func file_broker_proto_init() {
35723670 (*BrokerServerEnvelope_BrokerErrorMessage)(nil),
35733671 (*BrokerServerEnvelope_Shutdown)(nil),
35743672 (*BrokerServerEnvelope_PreviewPublication)(nil),
3673+ (*BrokerServerEnvelope_StreamWindowUpdate)(nil),
35753674 }
3576- file_broker_proto_msgTypes[19].OneofWrappers = []any{
3675+ file_broker_proto_msgTypes[20].OneofWrappers = []any{
35773676 (*SnapshotPart_Begin)(nil),
35783677 (*SnapshotPart_Daemon)(nil),
35793678 (*SnapshotPart_Session)(nil),
35803679 (*SnapshotPart_Tombstone)(nil),
35813680 (*SnapshotPart_End)(nil),
35823681 }
3583- file_broker_proto_msgTypes[35].OneofWrappers = []any{
3682+ file_broker_proto_msgTypes[36].OneofWrappers = []any{
35843683 (*PreviewPublication_Preview)(nil),
35853684 (*PreviewPublication_Error)(nil),
35863685 }
@@ -3590,7 +3689,7 @@ func file_broker_proto_init() {
35903689 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
35913690 RawDescriptor: unsafe.Slice(unsafe.StringData(file_broker_proto_rawDesc), len(file_broker_proto_rawDesc)),
35923691 NumEnums: 0,
3593- NumMessages: 38,
3692+ NumMessages: 39,
35943693 NumExtensions: 0,
35953694 NumServices: 0,
35963695 },
@@ -390,6 +390,7 @@ type BrokerClientEnvelope struct {
390 // *BrokerClientEnvelope_UpdateHostPolicy390 // *BrokerClientEnvelope_UpdateHostPolicy
391 // *BrokerClientEnvelope_StartPreview391 // *BrokerClientEnvelope_StartPreview
392 // *BrokerClientEnvelope_CancelPreview392 // *BrokerClientEnvelope_CancelPreview
393+ // *BrokerClientEnvelope_StreamWindowUpdate
393 Payload isBrokerClientEnvelope_Payload `protobuf_oneof:"payload"`394 Payload isBrokerClientEnvelope_Payload `protobuf_oneof:"payload"`
394 unknownFields protoimpl.UnknownFields395 unknownFields protoimpl.UnknownFields
395 sizeCache protoimpl.SizeCache396 sizeCache protoimpl.SizeCache
@@ -549,6 +550,15 @@ func (x *BrokerClientEnvelope) GetCancelPreview() *CancelPreview {
549 return nil550 return nil
550 }551 }
551 552
553+func (x *BrokerClientEnvelope) GetStreamWindowUpdate() *StreamWindowUpdate {
554+ if x != nil {
555+ if x, ok := x.Payload.(*BrokerClientEnvelope_StreamWindowUpdate); ok {
556+ return x.StreamWindowUpdate
557+ }
558+ }
559+ return nil
560+}
561+
552 type isBrokerClientEnvelope_Payload interface {562 type isBrokerClientEnvelope_Payload interface {
553 isBrokerClientEnvelope_Payload()563 isBrokerClientEnvelope_Payload()
554 }564 }
@@ -605,6 +615,10 @@ type BrokerClientEnvelope_CancelPreview struct {
605 CancelPreview *CancelPreview `protobuf:"bytes,113,opt,name=cancel_preview,json=cancelPreview,proto3,oneof"`615 CancelPreview *CancelPreview `protobuf:"bytes,113,opt,name=cancel_preview,json=cancelPreview,proto3,oneof"`
606 }616 }
607 617
618+type BrokerClientEnvelope_StreamWindowUpdate struct {
619+ StreamWindowUpdate *StreamWindowUpdate `protobuf:"bytes,114,opt,name=stream_window_update,json=streamWindowUpdate,proto3,oneof"`
620+}
621+
608 func (*BrokerClientEnvelope_Register) isBrokerClientEnvelope_Payload() {}622 func (*BrokerClientEnvelope_Register) isBrokerClientEnvelope_Payload() {}
609 623
610 func (*BrokerClientEnvelope_Subscribe) isBrokerClientEnvelope_Payload() {}624 func (*BrokerClientEnvelope_Subscribe) isBrokerClientEnvelope_Payload() {}
@@ -631,6 +645,8 @@ func (*BrokerClientEnvelope_StartPreview) isBrokerClientEnvelope_Payload() {}
631 645
632 func (*BrokerClientEnvelope_CancelPreview) isBrokerClientEnvelope_Payload() {}646 func (*BrokerClientEnvelope_CancelPreview) isBrokerClientEnvelope_Payload() {}
633 647
648+func (*BrokerClientEnvelope_StreamWindowUpdate) isBrokerClientEnvelope_Payload() {}
649+
634 // BrokerServerEnvelope is the closed server-to-client broker union. Exactly650 // BrokerServerEnvelope is the closed server-to-client broker union. Exactly
635 // one variant must be set per envelope.651 // one variant must be set per envelope.
636 type BrokerServerEnvelope struct {652 type BrokerServerEnvelope struct {
@@ -647,6 +663,7 @@ type BrokerServerEnvelope struct {
647 // *BrokerServerEnvelope_BrokerErrorMessage663 // *BrokerServerEnvelope_BrokerErrorMessage
648 // *BrokerServerEnvelope_Shutdown664 // *BrokerServerEnvelope_Shutdown
649 // *BrokerServerEnvelope_PreviewPublication665 // *BrokerServerEnvelope_PreviewPublication
666+ // *BrokerServerEnvelope_StreamWindowUpdate
650 Payload isBrokerServerEnvelope_Payload `protobuf_oneof:"payload"`667 Payload isBrokerServerEnvelope_Payload `protobuf_oneof:"payload"`
651 unknownFields protoimpl.UnknownFields668 unknownFields protoimpl.UnknownFields
652 sizeCache protoimpl.SizeCache669 sizeCache protoimpl.SizeCache
@@ -779,6 +796,15 @@ func (x *BrokerServerEnvelope) GetPreviewPublication() *PreviewPublication {
779 return nil796 return nil
780 }797 }
781 798
799+func (x *BrokerServerEnvelope) GetStreamWindowUpdate() *StreamWindowUpdate {
800+ if x != nil {
801+ if x, ok := x.Payload.(*BrokerServerEnvelope_StreamWindowUpdate); ok {
802+ return x.StreamWindowUpdate
803+ }
804+ }
805+ return nil
806+}
807+
782 type isBrokerServerEnvelope_Payload interface {808 type isBrokerServerEnvelope_Payload interface {
783 isBrokerServerEnvelope_Payload()809 isBrokerServerEnvelope_Payload()
784 }810 }
@@ -823,6 +849,10 @@ type BrokerServerEnvelope_PreviewPublication struct {
823 PreviewPublication *PreviewPublication `protobuf:"bytes,210,opt,name=preview_publication,json=previewPublication,proto3,oneof"`849 PreviewPublication *PreviewPublication `protobuf:"bytes,210,opt,name=preview_publication,json=previewPublication,proto3,oneof"`
824 }850 }
825 851
852+type BrokerServerEnvelope_StreamWindowUpdate struct {
853+ StreamWindowUpdate *StreamWindowUpdate `protobuf:"bytes,211,opt,name=stream_window_update,json=streamWindowUpdate,proto3,oneof"`
854+}
855+
826 func (*BrokerServerEnvelope_Registered) isBrokerServerEnvelope_Payload() {}856 func (*BrokerServerEnvelope_Registered) isBrokerServerEnvelope_Payload() {}
827 857
828 func (*BrokerServerEnvelope_SnapshotPart) isBrokerServerEnvelope_Payload() {}858 func (*BrokerServerEnvelope_SnapshotPart) isBrokerServerEnvelope_Payload() {}
@@ -843,6 +873,8 @@ func (*BrokerServerEnvelope_Shutdown) isBrokerServerEnvelope_Payload() {}
843 873
844 func (*BrokerServerEnvelope_PreviewPublication) isBrokerServerEnvelope_Payload() {}874 func (*BrokerServerEnvelope_PreviewPublication) isBrokerServerEnvelope_Payload() {}
845 875
876+func (*BrokerServerEnvelope_StreamWindowUpdate) isBrokerServerEnvelope_Payload() {}
877+
846 // Register opens one client connection to the broker.878 // Register opens one client connection to the broker.
847 type Register struct {879 type Register struct {
848 state protoimpl.MessageState `protogen:"open.v1"`880 state protoimpl.MessageState `protogen:"open.v1"`
@@ -1563,6 +1595,62 @@ func (x *CloseStream) GetRef() *BrokerStreamRef {
1563 return nil1595 return nil
1564 }1596 }
1565 1597
1598+// StreamWindowUpdate grants the peer credit to send more stream data on one
1599+// logical stream: the receiver consumed that much of its stream window. Each
1600+// direction of a stream has its own window, so a client grants credit for
1601+// ServerStreamData and the server grants credit for ClientStreamData.
1602+type StreamWindowUpdate struct {
1603+ state protoimpl.MessageState `protogen:"open.v1"`
1604+ Ref *BrokerStreamRef `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"`
1605+ Credit uint64 `protobuf:"varint,2,opt,name=credit,proto3" json:"credit,omitempty"`
1606+ unknownFields protoimpl.UnknownFields
1607+ sizeCache protoimpl.SizeCache
1608+}
1609+
1610+func (x *StreamWindowUpdate) Reset() {
1611+ *x = StreamWindowUpdate{}
1612+ mi := &file_broker_proto_msgTypes[18]
1613+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1614+ ms.StoreMessageInfo(mi)
1615+}
1616+
1617+func (x *StreamWindowUpdate) String() string {
1618+ return protoimpl.X.MessageStringOf(x)
1619+}
1620+
1621+func (*StreamWindowUpdate) ProtoMessage() {}
1622+
1623+func (x *StreamWindowUpdate) ProtoReflect() protoreflect.Message {
1624+ mi := &file_broker_proto_msgTypes[18]
1625+ if x != nil {
1626+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1627+ if ms.LoadMessageInfo() == nil {
1628+ ms.StoreMessageInfo(mi)
1629+ }
1630+ return ms
1631+ }
1632+ return mi.MessageOf(x)
1633+}
1634+
1635+// Deprecated: Use StreamWindowUpdate.ProtoReflect.Descriptor instead.
1636+func (*StreamWindowUpdate) Descriptor() ([]byte, []int) {
1637+ return file_broker_proto_rawDescGZIP(), []int{18}
1638+}
1639+
1640+func (x *StreamWindowUpdate) GetRef() *BrokerStreamRef {
1641+ if x != nil {
1642+ return x.Ref
1643+ }
1644+ return nil
1645+}
1646+
1647+func (x *StreamWindowUpdate) GetCredit() uint64 {
1648+ if x != nil {
1649+ return x.Credit
1650+ }
1651+ return 0
1652+}
1653+
1566 // Registered confirms one accepted client connection and its assigned scope.1654 // Registered confirms one accepted client connection and its assigned scope.
1567 type Registered struct {1655 type Registered struct {
1568 state protoimpl.MessageState `protogen:"open.v1"`1656 state protoimpl.MessageState `protogen:"open.v1"`
@@ -1575,7 +1663,7 @@ type Registered struct {
1575 1663
1576 func (x *Registered) Reset() {1664 func (x *Registered) Reset() {
1577 *x = Registered{}1665 *x = Registered{}
1578- mi := &file_broker_proto_msgTypes[18]1666+ mi := &file_broker_proto_msgTypes[19]
1579 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1667 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1580 ms.StoreMessageInfo(mi)1668 ms.StoreMessageInfo(mi)
1581 }1669 }
@@ -1587,7 +1675,7 @@ func (x *Registered) String() string {
1587 func (*Registered) ProtoMessage() {}1675 func (*Registered) ProtoMessage() {}
1588 1676
1589 func (x *Registered) ProtoReflect() protoreflect.Message {1677 func (x *Registered) ProtoReflect() protoreflect.Message {
1590- mi := &file_broker_proto_msgTypes[18]1678+ mi := &file_broker_proto_msgTypes[19]
1591 if x != nil {1679 if x != nil {
1592 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1680 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1593 if ms.LoadMessageInfo() == nil {1681 if ms.LoadMessageInfo() == nil {
@@ -1600,7 +1688,7 @@ func (x *Registered) ProtoReflect() protoreflect.Message {
1600 1688
1601 // Deprecated: Use Registered.ProtoReflect.Descriptor instead.1689 // Deprecated: Use Registered.ProtoReflect.Descriptor instead.
1602 func (*Registered) Descriptor() ([]byte, []int) {1690 func (*Registered) Descriptor() ([]byte, []int) {
1603- return file_broker_proto_rawDescGZIP(), []int{18}1691+ return file_broker_proto_rawDescGZIP(), []int{19}
1604 }1692 }
1605 1693
1606 func (x *Registered) GetScope() *BrokerScope {1694 func (x *Registered) GetScope() *BrokerScope {
@@ -1647,7 +1735,7 @@ type SnapshotPart struct {
1647 1735
1648 func (x *SnapshotPart) Reset() {1736 func (x *SnapshotPart) Reset() {
1649 *x = SnapshotPart{}1737 *x = SnapshotPart{}
1650- mi := &file_broker_proto_msgTypes[19]1738+ mi := &file_broker_proto_msgTypes[20]
1651 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1739 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1652 ms.StoreMessageInfo(mi)1740 ms.StoreMessageInfo(mi)
1653 }1741 }
@@ -1659,7 +1747,7 @@ func (x *SnapshotPart) String() string {
1659 func (*SnapshotPart) ProtoMessage() {}1747 func (*SnapshotPart) ProtoMessage() {}
1660 1748
1661 func (x *SnapshotPart) ProtoReflect() protoreflect.Message {1749 func (x *SnapshotPart) ProtoReflect() protoreflect.Message {
1662- mi := &file_broker_proto_msgTypes[19]1750+ mi := &file_broker_proto_msgTypes[20]
1663 if x != nil {1751 if x != nil {
1664 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1752 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1665 if ms.LoadMessageInfo() == nil {1753 if ms.LoadMessageInfo() == nil {
@@ -1672,7 +1760,7 @@ func (x *SnapshotPart) ProtoReflect() protoreflect.Message {
1672 1760
1673 // Deprecated: Use SnapshotPart.ProtoReflect.Descriptor instead.1761 // Deprecated: Use SnapshotPart.ProtoReflect.Descriptor instead.
1674 func (*SnapshotPart) Descriptor() ([]byte, []int) {1762 func (*SnapshotPart) Descriptor() ([]byte, []int) {
1675- return file_broker_proto_rawDescGZIP(), []int{19}1763+ return file_broker_proto_rawDescGZIP(), []int{20}
1676 }1764 }
1677 1765
1678 func (x *SnapshotPart) GetScope() *BrokerScope {1766 func (x *SnapshotPart) GetScope() *BrokerScope {
@@ -1805,7 +1893,7 @@ type SnapshotBegin struct {
1805 1893
1806 func (x *SnapshotBegin) Reset() {1894 func (x *SnapshotBegin) Reset() {
1807 *x = SnapshotBegin{}1895 *x = SnapshotBegin{}
1808- mi := &file_broker_proto_msgTypes[20]1896+ mi := &file_broker_proto_msgTypes[21]
1809 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1897 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1810 ms.StoreMessageInfo(mi)1898 ms.StoreMessageInfo(mi)
1811 }1899 }
@@ -1817,7 +1905,7 @@ func (x *SnapshotBegin) String() string {
1817 func (*SnapshotBegin) ProtoMessage() {}1905 func (*SnapshotBegin) ProtoMessage() {}
1818 1906
1819 func (x *SnapshotBegin) ProtoReflect() protoreflect.Message {1907 func (x *SnapshotBegin) ProtoReflect() protoreflect.Message {
1820- mi := &file_broker_proto_msgTypes[20]1908+ mi := &file_broker_proto_msgTypes[21]
1821 if x != nil {1909 if x != nil {
1822 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1910 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1823 if ms.LoadMessageInfo() == nil {1911 if ms.LoadMessageInfo() == nil {
@@ -1830,7 +1918,7 @@ func (x *SnapshotBegin) ProtoReflect() protoreflect.Message {
1830 1918
1831 // Deprecated: Use SnapshotBegin.ProtoReflect.Descriptor instead.1919 // Deprecated: Use SnapshotBegin.ProtoReflect.Descriptor instead.
1832 func (*SnapshotBegin) Descriptor() ([]byte, []int) {1920 func (*SnapshotBegin) Descriptor() ([]byte, []int) {
1833- return file_broker_proto_rawDescGZIP(), []int{20}1921+ return file_broker_proto_rawDescGZIP(), []int{21}
1834 }1922 }
1835 1923
1836 func (x *SnapshotBegin) GetHostCount() uint32 {1924 func (x *SnapshotBegin) GetHostCount() uint32 {
@@ -1905,7 +1993,7 @@ type SnapshotDaemon struct {
1905 1993
1906 func (x *SnapshotDaemon) Reset() {1994 func (x *SnapshotDaemon) Reset() {
1907 *x = SnapshotDaemon{}1995 *x = SnapshotDaemon{}
1908- mi := &file_broker_proto_msgTypes[21]1996+ mi := &file_broker_proto_msgTypes[22]
1909 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))1997 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1910 ms.StoreMessageInfo(mi)1998 ms.StoreMessageInfo(mi)
1911 }1999 }
@@ -1917,7 +2005,7 @@ func (x *SnapshotDaemon) String() string {
1917 func (*SnapshotDaemon) ProtoMessage() {}2005 func (*SnapshotDaemon) ProtoMessage() {}
1918 2006
1919 func (x *SnapshotDaemon) ProtoReflect() protoreflect.Message {2007 func (x *SnapshotDaemon) ProtoReflect() protoreflect.Message {
1920- mi := &file_broker_proto_msgTypes[21]2008+ mi := &file_broker_proto_msgTypes[22]
1921 if x != nil {2009 if x != nil {
1922 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2010 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1923 if ms.LoadMessageInfo() == nil {2011 if ms.LoadMessageInfo() == nil {
@@ -1930,7 +2018,7 @@ func (x *SnapshotDaemon) ProtoReflect() protoreflect.Message {
1930 2018
1931 // Deprecated: Use SnapshotDaemon.ProtoReflect.Descriptor instead.2019 // Deprecated: Use SnapshotDaemon.ProtoReflect.Descriptor instead.
1932 func (*SnapshotDaemon) Descriptor() ([]byte, []int) {2020 func (*SnapshotDaemon) Descriptor() ([]byte, []int) {
1933- return file_broker_proto_rawDescGZIP(), []int{21}2021+ return file_broker_proto_rawDescGZIP(), []int{22}
1934 }2022 }
1935 2023
1936 func (x *SnapshotDaemon) GetHostIndex() uint32 {2024 func (x *SnapshotDaemon) GetHostIndex() uint32 {
@@ -2095,7 +2183,7 @@ type SnapshotSession struct {
2095 2183
2096 func (x *SnapshotSession) Reset() {2184 func (x *SnapshotSession) Reset() {
2097 *x = SnapshotSession{}2185 *x = SnapshotSession{}
2098- mi := &file_broker_proto_msgTypes[22]2186+ mi := &file_broker_proto_msgTypes[23]
2099 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2187 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2100 ms.StoreMessageInfo(mi)2188 ms.StoreMessageInfo(mi)
2101 }2189 }
@@ -2107,7 +2195,7 @@ func (x *SnapshotSession) String() string {
2107 func (*SnapshotSession) ProtoMessage() {}2195 func (*SnapshotSession) ProtoMessage() {}
2108 2196
2109 func (x *SnapshotSession) ProtoReflect() protoreflect.Message {2197 func (x *SnapshotSession) ProtoReflect() protoreflect.Message {
2110- mi := &file_broker_proto_msgTypes[22]2198+ mi := &file_broker_proto_msgTypes[23]
2111 if x != nil {2199 if x != nil {
2112 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2200 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2113 if ms.LoadMessageInfo() == nil {2201 if ms.LoadMessageInfo() == nil {
@@ -2120,7 +2208,7 @@ func (x *SnapshotSession) ProtoReflect() protoreflect.Message {
2120 2208
2121 // Deprecated: Use SnapshotSession.ProtoReflect.Descriptor instead.2209 // Deprecated: Use SnapshotSession.ProtoReflect.Descriptor instead.
2122 func (*SnapshotSession) Descriptor() ([]byte, []int) {2210 func (*SnapshotSession) Descriptor() ([]byte, []int) {
2123- return file_broker_proto_rawDescGZIP(), []int{22}2211+ return file_broker_proto_rawDescGZIP(), []int{23}
2124 }2212 }
2125 2213
2126 func (x *SnapshotSession) GetHostIndex() uint32 {2214 func (x *SnapshotSession) GetHostIndex() uint32 {
@@ -2164,7 +2252,7 @@ type SnapshotTombstone struct {
2164 2252
2165 func (x *SnapshotTombstone) Reset() {2253 func (x *SnapshotTombstone) Reset() {
2166 *x = SnapshotTombstone{}2254 *x = SnapshotTombstone{}
2167- mi := &file_broker_proto_msgTypes[23]2255+ mi := &file_broker_proto_msgTypes[24]
2168 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2256 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2169 ms.StoreMessageInfo(mi)2257 ms.StoreMessageInfo(mi)
2170 }2258 }
@@ -2176,7 +2264,7 @@ func (x *SnapshotTombstone) String() string {
2176 func (*SnapshotTombstone) ProtoMessage() {}2264 func (*SnapshotTombstone) ProtoMessage() {}
2177 2265
2178 func (x *SnapshotTombstone) ProtoReflect() protoreflect.Message {2266 func (x *SnapshotTombstone) ProtoReflect() protoreflect.Message {
2179- mi := &file_broker_proto_msgTypes[23]2267+ mi := &file_broker_proto_msgTypes[24]
2180 if x != nil {2268 if x != nil {
2181 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2269 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2182 if ms.LoadMessageInfo() == nil {2270 if ms.LoadMessageInfo() == nil {
@@ -2189,7 +2277,7 @@ func (x *SnapshotTombstone) ProtoReflect() protoreflect.Message {
2189 2277
2190 // Deprecated: Use SnapshotTombstone.ProtoReflect.Descriptor instead.2278 // Deprecated: Use SnapshotTombstone.ProtoReflect.Descriptor instead.
2191 func (*SnapshotTombstone) Descriptor() ([]byte, []int) {2279 func (*SnapshotTombstone) Descriptor() ([]byte, []int) {
2192- return file_broker_proto_rawDescGZIP(), []int{23}2280+ return file_broker_proto_rawDescGZIP(), []int{24}
2193 }2281 }
2194 2282
2195 func (x *SnapshotTombstone) GetTombstoneIndex() uint32 {2283 func (x *SnapshotTombstone) GetTombstoneIndex() uint32 {
@@ -2222,7 +2310,7 @@ type SnapshotEnd struct {
2222 2310
2223 func (x *SnapshotEnd) Reset() {2311 func (x *SnapshotEnd) Reset() {
2224 *x = SnapshotEnd{}2312 *x = SnapshotEnd{}
2225- mi := &file_broker_proto_msgTypes[24]2313+ mi := &file_broker_proto_msgTypes[25]
2226 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2314 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2227 ms.StoreMessageInfo(mi)2315 ms.StoreMessageInfo(mi)
2228 }2316 }
@@ -2234,7 +2322,7 @@ func (x *SnapshotEnd) String() string {
2234 func (*SnapshotEnd) ProtoMessage() {}2322 func (*SnapshotEnd) ProtoMessage() {}
2235 2323
2236 func (x *SnapshotEnd) ProtoReflect() protoreflect.Message {2324 func (x *SnapshotEnd) ProtoReflect() protoreflect.Message {
2237- mi := &file_broker_proto_msgTypes[24]2325+ mi := &file_broker_proto_msgTypes[25]
2238 if x != nil {2326 if x != nil {
2239 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2327 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2240 if ms.LoadMessageInfo() == nil {2328 if ms.LoadMessageInfo() == nil {
@@ -2247,7 +2335,7 @@ func (x *SnapshotEnd) ProtoReflect() protoreflect.Message {
2247 2335
2248 // Deprecated: Use SnapshotEnd.ProtoReflect.Descriptor instead.2336 // Deprecated: Use SnapshotEnd.ProtoReflect.Descriptor instead.
2249 func (*SnapshotEnd) Descriptor() ([]byte, []int) {2337 func (*SnapshotEnd) Descriptor() ([]byte, []int) {
2250- return file_broker_proto_rawDescGZIP(), []int{24}2338+ return file_broker_proto_rawDescGZIP(), []int{25}
2251 }2339 }
2252 2340
2253 // OperationResult is exactly one completion for one admitted mutating2341 // OperationResult is exactly one completion for one admitted mutating
@@ -2270,7 +2358,7 @@ type OperationResult struct {
2270 2358
2271 func (x *OperationResult) Reset() {2359 func (x *OperationResult) Reset() {
2272 *x = OperationResult{}2360 *x = OperationResult{}
2273- mi := &file_broker_proto_msgTypes[25]2361+ mi := &file_broker_proto_msgTypes[26]
2274 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2362 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2275 ms.StoreMessageInfo(mi)2363 ms.StoreMessageInfo(mi)
2276 }2364 }
@@ -2282,7 +2370,7 @@ func (x *OperationResult) String() string {
2282 func (*OperationResult) ProtoMessage() {}2370 func (*OperationResult) ProtoMessage() {}
2283 2371
2284 func (x *OperationResult) ProtoReflect() protoreflect.Message {2372 func (x *OperationResult) ProtoReflect() protoreflect.Message {
2285- mi := &file_broker_proto_msgTypes[25]2373+ mi := &file_broker_proto_msgTypes[26]
2286 if x != nil {2374 if x != nil {
2287 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2375 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2288 if ms.LoadMessageInfo() == nil {2376 if ms.LoadMessageInfo() == nil {
@@ -2295,7 +2383,7 @@ func (x *OperationResult) ProtoReflect() protoreflect.Message {
2295 2383
2296 // Deprecated: Use OperationResult.ProtoReflect.Descriptor instead.2384 // Deprecated: Use OperationResult.ProtoReflect.Descriptor instead.
2297 func (*OperationResult) Descriptor() ([]byte, []int) {2385 func (*OperationResult) Descriptor() ([]byte, []int) {
2298- return file_broker_proto_rawDescGZIP(), []int{25}2386+ return file_broker_proto_rawDescGZIP(), []int{26}
2299 }2387 }
2300 2388
2301 func (x *OperationResult) GetScope() *BrokerScope {2389 func (x *OperationResult) GetScope() *BrokerScope {
@@ -2350,7 +2438,7 @@ type StreamOpened struct {
2350 2438
2351 func (x *StreamOpened) Reset() {2439 func (x *StreamOpened) Reset() {
2352 *x = StreamOpened{}2440 *x = StreamOpened{}
2353- mi := &file_broker_proto_msgTypes[26]2441+ mi := &file_broker_proto_msgTypes[27]
2354 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2442 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2355 ms.StoreMessageInfo(mi)2443 ms.StoreMessageInfo(mi)
2356 }2444 }
@@ -2362,7 +2450,7 @@ func (x *StreamOpened) String() string {
2362 func (*StreamOpened) ProtoMessage() {}2450 func (*StreamOpened) ProtoMessage() {}
2363 2451
2364 func (x *StreamOpened) ProtoReflect() protoreflect.Message {2452 func (x *StreamOpened) ProtoReflect() protoreflect.Message {
2365- mi := &file_broker_proto_msgTypes[26]2453+ mi := &file_broker_proto_msgTypes[27]
2366 if x != nil {2454 if x != nil {
2367 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2455 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2368 if ms.LoadMessageInfo() == nil {2456 if ms.LoadMessageInfo() == nil {
@@ -2375,7 +2463,7 @@ func (x *StreamOpened) ProtoReflect() protoreflect.Message {
2375 2463
2376 // Deprecated: Use StreamOpened.ProtoReflect.Descriptor instead.2464 // Deprecated: Use StreamOpened.ProtoReflect.Descriptor instead.
2377 func (*StreamOpened) Descriptor() ([]byte, []int) {2465 func (*StreamOpened) Descriptor() ([]byte, []int) {
2378- return file_broker_proto_rawDescGZIP(), []int{26}2466+ return file_broker_proto_rawDescGZIP(), []int{27}
2379 }2467 }
2380 2468
2381 func (x *StreamOpened) GetRef() *BrokerStreamRef {2469 func (x *StreamOpened) GetRef() *BrokerStreamRef {
@@ -2396,7 +2484,7 @@ type ServerStreamData struct {
2396 2484
2397 func (x *ServerStreamData) Reset() {2485 func (x *ServerStreamData) Reset() {
2398 *x = ServerStreamData{}2486 *x = ServerStreamData{}
2399- mi := &file_broker_proto_msgTypes[27]2487+ mi := &file_broker_proto_msgTypes[28]
2400 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2488 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2401 ms.StoreMessageInfo(mi)2489 ms.StoreMessageInfo(mi)
2402 }2490 }
@@ -2408,7 +2496,7 @@ func (x *ServerStreamData) String() string {
2408 func (*ServerStreamData) ProtoMessage() {}2496 func (*ServerStreamData) ProtoMessage() {}
2409 2497
2410 func (x *ServerStreamData) ProtoReflect() protoreflect.Message {2498 func (x *ServerStreamData) ProtoReflect() protoreflect.Message {
2411- mi := &file_broker_proto_msgTypes[27]2499+ mi := &file_broker_proto_msgTypes[28]
2412 if x != nil {2500 if x != nil {
2413 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2501 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2414 if ms.LoadMessageInfo() == nil {2502 if ms.LoadMessageInfo() == nil {
@@ -2421,7 +2509,7 @@ func (x *ServerStreamData) ProtoReflect() protoreflect.Message {
2421 2509
2422 // Deprecated: Use ServerStreamData.ProtoReflect.Descriptor instead.2510 // Deprecated: Use ServerStreamData.ProtoReflect.Descriptor instead.
2423 func (*ServerStreamData) Descriptor() ([]byte, []int) {2511 func (*ServerStreamData) Descriptor() ([]byte, []int) {
2424- return file_broker_proto_rawDescGZIP(), []int{27}2512+ return file_broker_proto_rawDescGZIP(), []int{28}
2425 }2513 }
2426 2514
2427 func (x *ServerStreamData) GetRef() *BrokerStreamRef {2515 func (x *ServerStreamData) GetRef() *BrokerStreamRef {
@@ -2450,7 +2538,7 @@ type StreamClosed struct {
2450 2538
2451 func (x *StreamClosed) Reset() {2539 func (x *StreamClosed) Reset() {
2452 *x = StreamClosed{}2540 *x = StreamClosed{}
2453- mi := &file_broker_proto_msgTypes[28]2541+ mi := &file_broker_proto_msgTypes[29]
2454 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2542 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2455 ms.StoreMessageInfo(mi)2543 ms.StoreMessageInfo(mi)
2456 }2544 }
@@ -2462,7 +2550,7 @@ func (x *StreamClosed) String() string {
2462 func (*StreamClosed) ProtoMessage() {}2550 func (*StreamClosed) ProtoMessage() {}
2463 2551
2464 func (x *StreamClosed) ProtoReflect() protoreflect.Message {2552 func (x *StreamClosed) ProtoReflect() protoreflect.Message {
2465- mi := &file_broker_proto_msgTypes[28]2553+ mi := &file_broker_proto_msgTypes[29]
2466 if x != nil {2554 if x != nil {
2467 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2555 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2468 if ms.LoadMessageInfo() == nil {2556 if ms.LoadMessageInfo() == nil {
@@ -2475,7 +2563,7 @@ func (x *StreamClosed) ProtoReflect() protoreflect.Message {
2475 2563
2476 // Deprecated: Use StreamClosed.ProtoReflect.Descriptor instead.2564 // Deprecated: Use StreamClosed.ProtoReflect.Descriptor instead.
2477 func (*StreamClosed) Descriptor() ([]byte, []int) {2565 func (*StreamClosed) Descriptor() ([]byte, []int) {
2478- return file_broker_proto_rawDescGZIP(), []int{28}2566+ return file_broker_proto_rawDescGZIP(), []int{29}
2479 }2567 }
2480 2568
2481 func (x *StreamClosed) GetRef() *BrokerStreamRef {2569 func (x *StreamClosed) GetRef() *BrokerStreamRef {
@@ -2504,7 +2592,7 @@ type Progress struct {
2504 2592
2505 func (x *Progress) Reset() {2593 func (x *Progress) Reset() {
2506 *x = Progress{}2594 *x = Progress{}
2507- mi := &file_broker_proto_msgTypes[29]2595+ mi := &file_broker_proto_msgTypes[30]
2508 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2596 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2509 ms.StoreMessageInfo(mi)2597 ms.StoreMessageInfo(mi)
2510 }2598 }
@@ -2516,7 +2604,7 @@ func (x *Progress) String() string {
2516 func (*Progress) ProtoMessage() {}2604 func (*Progress) ProtoMessage() {}
2517 2605
2518 func (x *Progress) ProtoReflect() protoreflect.Message {2606 func (x *Progress) ProtoReflect() protoreflect.Message {
2519- mi := &file_broker_proto_msgTypes[29]2607+ mi := &file_broker_proto_msgTypes[30]
2520 if x != nil {2608 if x != nil {
2521 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2609 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2522 if ms.LoadMessageInfo() == nil {2610 if ms.LoadMessageInfo() == nil {
@@ -2529,7 +2617,7 @@ func (x *Progress) ProtoReflect() protoreflect.Message {
2529 2617
2530 // Deprecated: Use Progress.ProtoReflect.Descriptor instead.2618 // Deprecated: Use Progress.ProtoReflect.Descriptor instead.
2531 func (*Progress) Descriptor() ([]byte, []int) {2619 func (*Progress) Descriptor() ([]byte, []int) {
2532- return file_broker_proto_rawDescGZIP(), []int{29}2620+ return file_broker_proto_rawDescGZIP(), []int{30}
2533 }2621 }
2534 2622
2535 func (x *Progress) GetRef() *BrokerStreamRef {2623 func (x *Progress) GetRef() *BrokerStreamRef {
@@ -2564,7 +2652,7 @@ type BrokerErrorMessage struct {
2564 2652
2565 func (x *BrokerErrorMessage) Reset() {2653 func (x *BrokerErrorMessage) Reset() {
2566 *x = BrokerErrorMessage{}2654 *x = BrokerErrorMessage{}
2567- mi := &file_broker_proto_msgTypes[30]2655+ mi := &file_broker_proto_msgTypes[31]
2568 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2656 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2569 ms.StoreMessageInfo(mi)2657 ms.StoreMessageInfo(mi)
2570 }2658 }
@@ -2576,7 +2664,7 @@ func (x *BrokerErrorMessage) String() string {
2576 func (*BrokerErrorMessage) ProtoMessage() {}2664 func (*BrokerErrorMessage) ProtoMessage() {}
2577 2665
2578 func (x *BrokerErrorMessage) ProtoReflect() protoreflect.Message {2666 func (x *BrokerErrorMessage) ProtoReflect() protoreflect.Message {
2579- mi := &file_broker_proto_msgTypes[30]2667+ mi := &file_broker_proto_msgTypes[31]
2580 if x != nil {2668 if x != nil {
2581 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2669 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2582 if ms.LoadMessageInfo() == nil {2670 if ms.LoadMessageInfo() == nil {
@@ -2589,7 +2677,7 @@ func (x *BrokerErrorMessage) ProtoReflect() protoreflect.Message {
2589 2677
2590 // Deprecated: Use BrokerErrorMessage.ProtoReflect.Descriptor instead.2678 // Deprecated: Use BrokerErrorMessage.ProtoReflect.Descriptor instead.
2591 func (*BrokerErrorMessage) Descriptor() ([]byte, []int) {2679 func (*BrokerErrorMessage) Descriptor() ([]byte, []int) {
2592- return file_broker_proto_rawDescGZIP(), []int{30}2680+ return file_broker_proto_rawDescGZIP(), []int{31}
2593 }2681 }
2594 2682
2595 func (x *BrokerErrorMessage) GetScope() *BrokerScope {2683 func (x *BrokerErrorMessage) GetScope() *BrokerScope {
@@ -2619,7 +2707,7 @@ type Shutdown struct {
2619 2707
2620 func (x *Shutdown) Reset() {2708 func (x *Shutdown) Reset() {
2621 *x = Shutdown{}2709 *x = Shutdown{}
2622- mi := &file_broker_proto_msgTypes[31]2710+ mi := &file_broker_proto_msgTypes[32]
2623 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2711 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2624 ms.StoreMessageInfo(mi)2712 ms.StoreMessageInfo(mi)
2625 }2713 }
@@ -2631,7 +2719,7 @@ func (x *Shutdown) String() string {
2631 func (*Shutdown) ProtoMessage() {}2719 func (*Shutdown) ProtoMessage() {}
2632 2720
2633 func (x *Shutdown) ProtoReflect() protoreflect.Message {2721 func (x *Shutdown) ProtoReflect() protoreflect.Message {
2634- mi := &file_broker_proto_msgTypes[31]2722+ mi := &file_broker_proto_msgTypes[32]
2635 if x != nil {2723 if x != nil {
2636 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2724 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2637 if ms.LoadMessageInfo() == nil {2725 if ms.LoadMessageInfo() == nil {
@@ -2644,7 +2732,7 @@ func (x *Shutdown) ProtoReflect() protoreflect.Message {
2644 2732
2645 // Deprecated: Use Shutdown.ProtoReflect.Descriptor instead.2733 // Deprecated: Use Shutdown.ProtoReflect.Descriptor instead.
2646 func (*Shutdown) Descriptor() ([]byte, []int) {2734 func (*Shutdown) Descriptor() ([]byte, []int) {
2647- return file_broker_proto_rawDescGZIP(), []int{31}2735+ return file_broker_proto_rawDescGZIP(), []int{32}
2648 }2736 }
2649 2737
2650 func (x *Shutdown) GetScope() *BrokerScope {2738 func (x *Shutdown) GetScope() *BrokerScope {
@@ -2683,7 +2771,7 @@ type PreviewRoute struct {
2683 2771
2684 func (x *PreviewRoute) Reset() {2772 func (x *PreviewRoute) Reset() {
2685 *x = PreviewRoute{}2773 *x = PreviewRoute{}
2686- mi := &file_broker_proto_msgTypes[32]2774+ mi := &file_broker_proto_msgTypes[33]
2687 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2775 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2688 ms.StoreMessageInfo(mi)2776 ms.StoreMessageInfo(mi)
2689 }2777 }
@@ -2695,7 +2783,7 @@ func (x *PreviewRoute) String() string {
2695 func (*PreviewRoute) ProtoMessage() {}2783 func (*PreviewRoute) ProtoMessage() {}
2696 2784
2697 func (x *PreviewRoute) ProtoReflect() protoreflect.Message {2785 func (x *PreviewRoute) ProtoReflect() protoreflect.Message {
2698- mi := &file_broker_proto_msgTypes[32]2786+ mi := &file_broker_proto_msgTypes[33]
2699 if x != nil {2787 if x != nil {
2700 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2788 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2701 if ms.LoadMessageInfo() == nil {2789 if ms.LoadMessageInfo() == nil {
@@ -2708,7 +2796,7 @@ func (x *PreviewRoute) ProtoReflect() protoreflect.Message {
2708 2796
2709 // Deprecated: Use PreviewRoute.ProtoReflect.Descriptor instead.2797 // Deprecated: Use PreviewRoute.ProtoReflect.Descriptor instead.
2710 func (*PreviewRoute) Descriptor() ([]byte, []int) {2798 func (*PreviewRoute) Descriptor() ([]byte, []int) {
2711- return file_broker_proto_rawDescGZIP(), []int{32}2799+ return file_broker_proto_rawDescGZIP(), []int{33}
2712 }2800 }
2713 2801
2714 func (x *PreviewRoute) GetLocal() bool {2802 func (x *PreviewRoute) GetLocal() bool {
@@ -2756,7 +2844,7 @@ type StartPreview struct {
2756 2844
2757 func (x *StartPreview) Reset() {2845 func (x *StartPreview) Reset() {
2758 *x = StartPreview{}2846 *x = StartPreview{}
2759- mi := &file_broker_proto_msgTypes[33]2847+ mi := &file_broker_proto_msgTypes[34]
2760 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2848 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2761 ms.StoreMessageInfo(mi)2849 ms.StoreMessageInfo(mi)
2762 }2850 }
@@ -2768,7 +2856,7 @@ func (x *StartPreview) String() string {
2768 func (*StartPreview) ProtoMessage() {}2856 func (*StartPreview) ProtoMessage() {}
2769 2857
2770 func (x *StartPreview) ProtoReflect() protoreflect.Message {2858 func (x *StartPreview) ProtoReflect() protoreflect.Message {
2771- mi := &file_broker_proto_msgTypes[33]2859+ mi := &file_broker_proto_msgTypes[34]
2772 if x != nil {2860 if x != nil {
2773 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2861 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2774 if ms.LoadMessageInfo() == nil {2862 if ms.LoadMessageInfo() == nil {
@@ -2781,7 +2869,7 @@ func (x *StartPreview) ProtoReflect() protoreflect.Message {
2781 2869
2782 // Deprecated: Use StartPreview.ProtoReflect.Descriptor instead.2870 // Deprecated: Use StartPreview.ProtoReflect.Descriptor instead.
2783 func (*StartPreview) Descriptor() ([]byte, []int) {2871 func (*StartPreview) Descriptor() ([]byte, []int) {
2784- return file_broker_proto_rawDescGZIP(), []int{33}2872+ return file_broker_proto_rawDescGZIP(), []int{34}
2785 }2873 }
2786 2874
2787 func (x *StartPreview) GetScope() *BrokerScope {2875 func (x *StartPreview) GetScope() *BrokerScope {
@@ -2823,7 +2911,7 @@ type CancelPreview struct {
2823 2911
2824 func (x *CancelPreview) Reset() {2912 func (x *CancelPreview) Reset() {
2825 *x = CancelPreview{}2913 *x = CancelPreview{}
2826- mi := &file_broker_proto_msgTypes[34]2914+ mi := &file_broker_proto_msgTypes[35]
2827 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2915 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2828 ms.StoreMessageInfo(mi)2916 ms.StoreMessageInfo(mi)
2829 }2917 }
@@ -2835,7 +2923,7 @@ func (x *CancelPreview) String() string {
2835 func (*CancelPreview) ProtoMessage() {}2923 func (*CancelPreview) ProtoMessage() {}
2836 2924
2837 func (x *CancelPreview) ProtoReflect() protoreflect.Message {2925 func (x *CancelPreview) ProtoReflect() protoreflect.Message {
2838- mi := &file_broker_proto_msgTypes[34]2926+ mi := &file_broker_proto_msgTypes[35]
2839 if x != nil {2927 if x != nil {
2840 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2928 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2841 if ms.LoadMessageInfo() == nil {2929 if ms.LoadMessageInfo() == nil {
@@ -2848,7 +2936,7 @@ func (x *CancelPreview) ProtoReflect() protoreflect.Message {
2848 2936
2849 // Deprecated: Use CancelPreview.ProtoReflect.Descriptor instead.2937 // Deprecated: Use CancelPreview.ProtoReflect.Descriptor instead.
2850 func (*CancelPreview) Descriptor() ([]byte, []int) {2938 func (*CancelPreview) Descriptor() ([]byte, []int) {
2851- return file_broker_proto_rawDescGZIP(), []int{34}2939+ return file_broker_proto_rawDescGZIP(), []int{35}
2852 }2940 }
2853 2941
2854 func (x *CancelPreview) GetScope() *BrokerScope {2942 func (x *CancelPreview) GetScope() *BrokerScope {
@@ -2882,7 +2970,7 @@ type PreviewPublication struct {
2882 2970
2883 func (x *PreviewPublication) Reset() {2971 func (x *PreviewPublication) Reset() {
2884 *x = PreviewPublication{}2972 *x = PreviewPublication{}
2885- mi := &file_broker_proto_msgTypes[35]2973+ mi := &file_broker_proto_msgTypes[36]
2886 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2974 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2887 ms.StoreMessageInfo(mi)2975 ms.StoreMessageInfo(mi)
2888 }2976 }
@@ -2894,7 +2982,7 @@ func (x *PreviewPublication) String() string {
2894 func (*PreviewPublication) ProtoMessage() {}2982 func (*PreviewPublication) ProtoMessage() {}
2895 2983
2896 func (x *PreviewPublication) ProtoReflect() protoreflect.Message {2984 func (x *PreviewPublication) ProtoReflect() protoreflect.Message {
2897- mi := &file_broker_proto_msgTypes[35]2985+ mi := &file_broker_proto_msgTypes[36]
2898 if x != nil {2986 if x != nil {
2899 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))2987 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2900 if ms.LoadMessageInfo() == nil {2988 if ms.LoadMessageInfo() == nil {
@@ -2907,7 +2995,7 @@ func (x *PreviewPublication) ProtoReflect() protoreflect.Message {
2907 2995
2908 // Deprecated: Use PreviewPublication.ProtoReflect.Descriptor instead.2996 // Deprecated: Use PreviewPublication.ProtoReflect.Descriptor instead.
2909 func (*PreviewPublication) Descriptor() ([]byte, []int) {2997 func (*PreviewPublication) Descriptor() ([]byte, []int) {
2910- return file_broker_proto_rawDescGZIP(), []int{35}2998+ return file_broker_proto_rawDescGZIP(), []int{36}
2911 }2999 }
2912 3000
2913 func (x *PreviewPublication) GetScope() *BrokerScope {3001 func (x *PreviewPublication) GetScope() *BrokerScope {
@@ -2987,7 +3075,7 @@ type BrokerCatalogSession struct {
2987 3075
2988 func (x *BrokerCatalogSession) Reset() {3076 func (x *BrokerCatalogSession) Reset() {
2989 *x = BrokerCatalogSession{}3077 *x = BrokerCatalogSession{}
2990- mi := &file_broker_proto_msgTypes[36]3078+ mi := &file_broker_proto_msgTypes[37]
2991 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))3079 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
2992 ms.StoreMessageInfo(mi)3080 ms.StoreMessageInfo(mi)
2993 }3081 }
@@ -2999,7 +3087,7 @@ func (x *BrokerCatalogSession) String() string {
2999 func (*BrokerCatalogSession) ProtoMessage() {}3087 func (*BrokerCatalogSession) ProtoMessage() {}
3000 3088
3001 func (x *BrokerCatalogSession) ProtoReflect() protoreflect.Message {3089 func (x *BrokerCatalogSession) ProtoReflect() protoreflect.Message {
3002- mi := &file_broker_proto_msgTypes[36]3090+ mi := &file_broker_proto_msgTypes[37]
3003 if x != nil {3091 if x != nil {
3004 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))3092 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
3005 if ms.LoadMessageInfo() == nil {3093 if ms.LoadMessageInfo() == nil {
@@ -3012,7 +3100,7 @@ func (x *BrokerCatalogSession) ProtoReflect() protoreflect.Message {
3012 3100
3013 // Deprecated: Use BrokerCatalogSession.ProtoReflect.Descriptor instead.3101 // Deprecated: Use BrokerCatalogSession.ProtoReflect.Descriptor instead.
3014 func (*BrokerCatalogSession) Descriptor() ([]byte, []int) {3102 func (*BrokerCatalogSession) Descriptor() ([]byte, []int) {
3015- return file_broker_proto_rawDescGZIP(), []int{36}3103+ return file_broker_proto_rawDescGZIP(), []int{37}
3016 }3104 }
3017 3105
3018 func (x *BrokerCatalogSession) GetLifecycleId() []byte {3106 func (x *BrokerCatalogSession) GetLifecycleId() []byte {
@@ -3092,7 +3180,7 @@ type BrokerCatalogTab struct {
3092 3180
3093 func (x *BrokerCatalogTab) Reset() {3181 func (x *BrokerCatalogTab) Reset() {
3094 *x = BrokerCatalogTab{}3182 *x = BrokerCatalogTab{}
3095- mi := &file_broker_proto_msgTypes[37]3183+ mi := &file_broker_proto_msgTypes[38]
3096 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))3184 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
3097 ms.StoreMessageInfo(mi)3185 ms.StoreMessageInfo(mi)
3098 }3186 }
@@ -3104,7 +3192,7 @@ func (x *BrokerCatalogTab) String() string {
3104 func (*BrokerCatalogTab) ProtoMessage() {}3192 func (*BrokerCatalogTab) ProtoMessage() {}
3105 3193
3106 func (x *BrokerCatalogTab) ProtoReflect() protoreflect.Message {3194 func (x *BrokerCatalogTab) ProtoReflect() protoreflect.Message {
3107- mi := &file_broker_proto_msgTypes[37]3195+ mi := &file_broker_proto_msgTypes[38]
3108 if x != nil {3196 if x != nil {
3109 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))3197 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
3110 if ms.LoadMessageInfo() == nil {3198 if ms.LoadMessageInfo() == nil {
@@ -3117,7 +3205,7 @@ func (x *BrokerCatalogTab) ProtoReflect() protoreflect.Message {
3117 3205
3118 // Deprecated: Use BrokerCatalogTab.ProtoReflect.Descriptor instead.3206 // Deprecated: Use BrokerCatalogTab.ProtoReflect.Descriptor instead.
3119 func (*BrokerCatalogTab) Descriptor() ([]byte, []int) {3207 func (*BrokerCatalogTab) Descriptor() ([]byte, []int) {
3120- return file_broker_proto_rawDescGZIP(), []int{37}3208+ return file_broker_proto_rawDescGZIP(), []int{38}
3121 }3209 }
3122 3210
3123 func (x *BrokerCatalogTab) GetId() string {3211 func (x *BrokerCatalogTab) GetId() string {
@@ -3181,7 +3269,7 @@ const file_broker_proto_rawDesc = "" +
3181 "\ffailure_kind\x18\x04 \x01(\rR\vfailureKind\"A\n" +3269 "\ffailure_kind\x18\x04 \x01(\rR\vfailureKind\"A\n" +
3182 "\x0fBrokerTimestamp\x12\x18\n" +3270 "\x0fBrokerTimestamp\x12\x18\n" +
3183 "\aseconds\x18\x01 \x01(\x03R\aseconds\x12\x14\n" +3271 "\aseconds\x18\x01 \x01(\x03R\aseconds\x12\x14\n" +
3184- "\x05nanos\x18\x02 \x01(\x05R\x05nanos\"\xc2\x06\n" +3272+ "\x05nanos\x18\x02 \x01(\x05R\x05nanos\"\x97\a\n" +
3185 "\x14BrokerClientEnvelope\x123\n" +3273 "\x14BrokerClientEnvelope\x123\n" +
3186 "\bregister\x18e \x01(\v2\x15.vev.wire.v1.RegisterH\x00R\bregister\x126\n" +3274 "\bregister\x18e \x01(\v2\x15.vev.wire.v1.RegisterH\x00R\bregister\x126\n" +
3187 "\tsubscribe\x18f \x01(\v2\x16.vev.wire.v1.SubscribeH\x00R\tsubscribe\x12-\n" +3275 "\tsubscribe\x18f \x01(\v2\x16.vev.wire.v1.SubscribeH\x00R\tsubscribe\x12-\n" +
@@ -3197,8 +3285,9 @@ const file_broker_proto_rawDesc = "" +
3197 "\fclose_stream\x18n \x01(\v2\x18.vev.wire.v1.CloseStreamH\x00R\vcloseStream\x12M\n" +3285 "\fclose_stream\x18n \x01(\v2\x18.vev.wire.v1.CloseStreamH\x00R\vcloseStream\x12M\n" +
3198 "\x12update_host_policy\x18o \x01(\v2\x1d.vev.wire.v1.UpdateHostPolicyH\x00R\x10updateHostPolicy\x12@\n" +3286 "\x12update_host_policy\x18o \x01(\v2\x1d.vev.wire.v1.UpdateHostPolicyH\x00R\x10updateHostPolicy\x12@\n" +
3199 "\rstart_preview\x18p \x01(\v2\x19.vev.wire.v1.StartPreviewH\x00R\fstartPreview\x12C\n" +3287 "\rstart_preview\x18p \x01(\v2\x19.vev.wire.v1.StartPreviewH\x00R\fstartPreview\x12C\n" +
3200- "\x0ecancel_preview\x18q \x01(\v2\x1a.vev.wire.v1.CancelPreviewH\x00R\rcancelPreviewB\t\n" +3288+ "\x0ecancel_preview\x18q \x01(\v2\x1a.vev.wire.v1.CancelPreviewH\x00R\rcancelPreview\x12S\n" +
3201- "\apayload\"\xd9\x05\n" +3289+ "\x14stream_window_update\x18r \x01(\v2\x1f.vev.wire.v1.StreamWindowUpdateH\x00R\x12streamWindowUpdateB\t\n" +
3290+ "\apayload\"\xaf\x06\n" +
3202 "\x14BrokerServerEnvelope\x12:\n" +3291 "\x14BrokerServerEnvelope\x12:\n" +
3203 "\n" +3292 "\n" +
3204 "registered\x18\xc9\x01 \x01(\v2\x17.vev.wire.v1.RegisteredH\x00R\n" +3293 "registered\x18\xc9\x01 \x01(\v2\x17.vev.wire.v1.RegisteredH\x00R\n" +
@@ -3211,7 +3300,8 @@ const file_broker_proto_rawDesc = "" +
3211 "\bprogress\x18\xcf\x01 \x01(\v2\x15.vev.wire.v1.ProgressH\x00R\bprogress\x12T\n" +3300 "\bprogress\x18\xcf\x01 \x01(\v2\x15.vev.wire.v1.ProgressH\x00R\bprogress\x12T\n" +
3212 "\x14broker_error_message\x18\xd0\x01 \x01(\v2\x1f.vev.wire.v1.BrokerErrorMessageH\x00R\x12brokerErrorMessage\x124\n" +3301 "\x14broker_error_message\x18\xd0\x01 \x01(\v2\x1f.vev.wire.v1.BrokerErrorMessageH\x00R\x12brokerErrorMessage\x124\n" +
3213 "\bshutdown\x18\xd1\x01 \x01(\v2\x15.vev.wire.v1.ShutdownH\x00R\bshutdown\x12S\n" +3302 "\bshutdown\x18\xd1\x01 \x01(\v2\x15.vev.wire.v1.ShutdownH\x00R\bshutdown\x12S\n" +
3214- "\x13preview_publication\x18\xd2\x01 \x01(\v2\x1f.vev.wire.v1.PreviewPublicationH\x00R\x12previewPublicationB\t\n" +3303+ "\x13preview_publication\x18\xd2\x01 \x01(\v2\x1f.vev.wire.v1.PreviewPublicationH\x00R\x12previewPublication\x12T\n" +
3304+ "\x14stream_window_update\x18\xd3\x01 \x01(\v2\x1f.vev.wire.v1.StreamWindowUpdateH\x00R\x12streamWindowUpdateB\t\n" +
3215 "\apayload\" \n" +3305 "\apayload\" \n" +
3216 "\bRegister\x12\x14\n" +3306 "\bRegister\x12\x14\n" +
3217 "\x05build\x18\x01 \x01(\tR\x05build\"u\n" +3307 "\x05build\x18\x01 \x01(\tR\x05build\"u\n" +
@@ -3268,7 +3358,10 @@ const file_broker_proto_rawDesc = "" +
3268 "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\x12\x12\n" +3358 "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\x12\x12\n" +
3269 "\x04data\x18\x02 \x01(\fR\x04data\"=\n" +3359 "\x04data\x18\x02 \x01(\fR\x04data\"=\n" +
3270 "\vCloseStream\x12.\n" +3360 "\vCloseStream\x12.\n" +
3271- "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\"n\n" +3361+ "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\"\\\n" +
3362+ "\x12StreamWindowUpdate\x12.\n" +
3363+ "\x03ref\x18\x01 \x01(\v2\x1c.vev.wire.v1.BrokerStreamRefR\x03ref\x12\x16\n" +
3364+ "\x06credit\x18\x02 \x01(\x04R\x06credit\"n\n" +
3272 "\n" +3365 "\n" +
3273 "Registered\x12.\n" +3366 "Registered\x12.\n" +
3274 "\x05scope\x18\x01 \x01(\v2\x18.vev.wire.v1.BrokerScopeR\x05scope\x12\x14\n" +3367 "\x05scope\x18\x01 \x01(\v2\x18.vev.wire.v1.BrokerScopeR\x05scope\x12\x14\n" +
@@ -3409,7 +3502,7 @@ func file_broker_proto_rawDescGZIP() []byte {
3409 return file_broker_proto_rawDescData3502 return file_broker_proto_rawDescData
3410 }3503 }
3411 3504
3412-var file_broker_proto_msgTypes = make([]protoimpl.MessageInfo, 38)3505+var file_broker_proto_msgTypes = make([]protoimpl.MessageInfo, 39)
3413 var file_broker_proto_goTypes = []any{3506 var file_broker_proto_goTypes = []any{
3414 (*BrokerScope)(nil), // 0: vev.wire.v1.BrokerScope3507 (*BrokerScope)(nil), // 0: vev.wire.v1.BrokerScope
3415 (*BrokerStreamRef)(nil), // 1: vev.wire.v1.BrokerStreamRef3508 (*BrokerStreamRef)(nil), // 1: vev.wire.v1.BrokerStreamRef
@@ -3429,30 +3522,31 @@ var file_broker_proto_goTypes = []any{
3429 (*OpenStream)(nil), // 15: vev.wire.v1.OpenStream3522 (*OpenStream)(nil), // 15: vev.wire.v1.OpenStream
3430 (*ClientStreamData)(nil), // 16: vev.wire.v1.ClientStreamData3523 (*ClientStreamData)(nil), // 16: vev.wire.v1.ClientStreamData
3431 (*CloseStream)(nil), // 17: vev.wire.v1.CloseStream3524 (*CloseStream)(nil), // 17: vev.wire.v1.CloseStream
3432- (*Registered)(nil), // 18: vev.wire.v1.Registered3525+ (*StreamWindowUpdate)(nil), // 18: vev.wire.v1.StreamWindowUpdate
3433- (*SnapshotPart)(nil), // 19: vev.wire.v1.SnapshotPart3526+ (*Registered)(nil), // 19: vev.wire.v1.Registered
3434- (*SnapshotBegin)(nil), // 20: vev.wire.v1.SnapshotBegin3527+ (*SnapshotPart)(nil), // 20: vev.wire.v1.SnapshotPart
3435- (*SnapshotDaemon)(nil), // 21: vev.wire.v1.SnapshotDaemon3528+ (*SnapshotBegin)(nil), // 21: vev.wire.v1.SnapshotBegin
3436- (*SnapshotSession)(nil), // 22: vev.wire.v1.SnapshotSession3529+ (*SnapshotDaemon)(nil), // 22: vev.wire.v1.SnapshotDaemon
3437- (*SnapshotTombstone)(nil), // 23: vev.wire.v1.SnapshotTombstone3530+ (*SnapshotSession)(nil), // 23: vev.wire.v1.SnapshotSession
3438- (*SnapshotEnd)(nil), // 24: vev.wire.v1.SnapshotEnd3531+ (*SnapshotTombstone)(nil), // 24: vev.wire.v1.SnapshotTombstone
3439- (*OperationResult)(nil), // 25: vev.wire.v1.OperationResult3532+ (*SnapshotEnd)(nil), // 25: vev.wire.v1.SnapshotEnd
3440- (*StreamOpened)(nil), // 26: vev.wire.v1.StreamOpened3533+ (*OperationResult)(nil), // 26: vev.wire.v1.OperationResult
3441- (*ServerStreamData)(nil), // 27: vev.wire.v1.ServerStreamData3534+ (*StreamOpened)(nil), // 27: vev.wire.v1.StreamOpened
3442- (*StreamClosed)(nil), // 28: vev.wire.v1.StreamClosed3535+ (*ServerStreamData)(nil), // 28: vev.wire.v1.ServerStreamData
3443- (*Progress)(nil), // 29: vev.wire.v1.Progress3536+ (*StreamClosed)(nil), // 29: vev.wire.v1.StreamClosed
3444- (*BrokerErrorMessage)(nil), // 30: vev.wire.v1.BrokerErrorMessage3537+ (*Progress)(nil), // 30: vev.wire.v1.Progress
3445- (*Shutdown)(nil), // 31: vev.wire.v1.Shutdown3538+ (*BrokerErrorMessage)(nil), // 31: vev.wire.v1.BrokerErrorMessage
3446- (*PreviewRoute)(nil), // 32: vev.wire.v1.PreviewRoute3539+ (*Shutdown)(nil), // 32: vev.wire.v1.Shutdown
3447- (*StartPreview)(nil), // 33: vev.wire.v1.StartPreview3540+ (*PreviewRoute)(nil), // 33: vev.wire.v1.PreviewRoute
3448- (*CancelPreview)(nil), // 34: vev.wire.v1.CancelPreview3541+ (*StartPreview)(nil), // 34: vev.wire.v1.StartPreview
3449- (*PreviewPublication)(nil), // 35: vev.wire.v1.PreviewPublication3542+ (*CancelPreview)(nil), // 35: vev.wire.v1.CancelPreview
3450- (*BrokerCatalogSession)(nil), // 36: vev.wire.v1.BrokerCatalogSession3543+ (*PreviewPublication)(nil), // 36: vev.wire.v1.PreviewPublication
3451- (*BrokerCatalogTab)(nil), // 37: vev.wire.v1.BrokerCatalogTab3544+ (*BrokerCatalogSession)(nil), // 37: vev.wire.v1.BrokerCatalogSession
3452- (*RemoteRegistration)(nil), // 38: vev.wire.v1.RemoteRegistration3545+ (*BrokerCatalogTab)(nil), // 38: vev.wire.v1.BrokerCatalogTab
3453- (*ExactTarget)(nil), // 39: vev.wire.v1.ExactTarget3546+ (*RemoteRegistration)(nil), // 39: vev.wire.v1.RemoteRegistration
3454- (*RemotePreviewRequest)(nil), // 40: vev.wire.v1.RemotePreviewRequest3547+ (*ExactTarget)(nil), // 40: vev.wire.v1.ExactTarget
3455- (*RemotePreview)(nil), // 41: vev.wire.v1.RemotePreview3548+ (*RemotePreviewRequest)(nil), // 41: vev.wire.v1.RemotePreviewRequest
3549+ (*RemotePreview)(nil), // 42: vev.wire.v1.RemotePreview
3456 }3550 }
3457 var file_broker_proto_depIdxs = []int32{3551 var file_broker_proto_depIdxs = []int32{
3458 0, // 0: vev.wire.v1.BrokerStreamRef.scope:type_name -> vev.wire.v1.BrokerScope3552 0, // 0: vev.wire.v1.BrokerStreamRef.scope:type_name -> vev.wire.v1.BrokerScope
@@ -3467,76 +3561,79 @@ var file_broker_proto_depIdxs = []int32{
3467 16, // 9: vev.wire.v1.BrokerClientEnvelope.client_stream_data:type_name -> vev.wire.v1.ClientStreamData3561 16, // 9: vev.wire.v1.BrokerClientEnvelope.client_stream_data:type_name -> vev.wire.v1.ClientStreamData
3468 17, // 10: vev.wire.v1.BrokerClientEnvelope.close_stream:type_name -> vev.wire.v1.CloseStream3562 17, // 10: vev.wire.v1.BrokerClientEnvelope.close_stream:type_name -> vev.wire.v1.CloseStream
3469 13, // 11: vev.wire.v1.BrokerClientEnvelope.update_host_policy:type_name -> vev.wire.v1.UpdateHostPolicy3563 13, // 11: vev.wire.v1.BrokerClientEnvelope.update_host_policy:type_name -> vev.wire.v1.UpdateHostPolicy
3470- 33, // 12: vev.wire.v1.BrokerClientEnvelope.start_preview:type_name -> vev.wire.v1.StartPreview3564+ 34, // 12: vev.wire.v1.BrokerClientEnvelope.start_preview:type_name -> vev.wire.v1.StartPreview
3471- 34, // 13: vev.wire.v1.BrokerClientEnvelope.cancel_preview:type_name -> vev.wire.v1.CancelPreview3565+ 35, // 13: vev.wire.v1.BrokerClientEnvelope.cancel_preview:type_name -> vev.wire.v1.CancelPreview
3472- 18, // 14: vev.wire.v1.BrokerServerEnvelope.registered:type_name -> vev.wire.v1.Registered3566+ 18, // 14: vev.wire.v1.BrokerClientEnvelope.stream_window_update:type_name -> vev.wire.v1.StreamWindowUpdate
3473- 19, // 15: vev.wire.v1.BrokerServerEnvelope.snapshot_part:type_name -> vev.wire.v1.SnapshotPart3567+ 19, // 15: vev.wire.v1.BrokerServerEnvelope.registered:type_name -> vev.wire.v1.Registered
3474- 25, // 16: vev.wire.v1.BrokerServerEnvelope.operation_result:type_name -> vev.wire.v1.OperationResult3568+ 20, // 16: vev.wire.v1.BrokerServerEnvelope.snapshot_part:type_name -> vev.wire.v1.SnapshotPart
3475- 26, // 17: vev.wire.v1.BrokerServerEnvelope.stream_opened:type_name -> vev.wire.v1.StreamOpened3569+ 26, // 17: vev.wire.v1.BrokerServerEnvelope.operation_result:type_name -> vev.wire.v1.OperationResult
3476- 27, // 18: vev.wire.v1.BrokerServerEnvelope.server_stream_data:type_name -> vev.wire.v1.ServerStreamData3570+ 27, // 18: vev.wire.v1.BrokerServerEnvelope.stream_opened:type_name -> vev.wire.v1.StreamOpened
3477- 28, // 19: vev.wire.v1.BrokerServerEnvelope.stream_closed:type_name -> vev.wire.v1.StreamClosed3571+ 28, // 19: vev.wire.v1.BrokerServerEnvelope.server_stream_data:type_name -> vev.wire.v1.ServerStreamData
3478- 29, // 20: vev.wire.v1.BrokerServerEnvelope.progress:type_name -> vev.wire.v1.Progress3572+ 29, // 20: vev.wire.v1.BrokerServerEnvelope.stream_closed:type_name -> vev.wire.v1.StreamClosed
3479- 30, // 21: vev.wire.v1.BrokerServerEnvelope.broker_error_message:type_name -> vev.wire.v1.BrokerErrorMessage3573+ 30, // 21: vev.wire.v1.BrokerServerEnvelope.progress:type_name -> vev.wire.v1.Progress
3480- 31, // 22: vev.wire.v1.BrokerServerEnvelope.shutdown:type_name -> vev.wire.v1.Shutdown3574+ 31, // 22: vev.wire.v1.BrokerServerEnvelope.broker_error_message:type_name -> vev.wire.v1.BrokerErrorMessage
3481- 35, // 23: vev.wire.v1.BrokerServerEnvelope.preview_publication:type_name -> vev.wire.v1.PreviewPublication3575+ 32, // 23: vev.wire.v1.BrokerServerEnvelope.shutdown:type_name -> vev.wire.v1.Shutdown
3482- 0, // 24: vev.wire.v1.Subscribe.scope:type_name -> vev.wire.v1.BrokerScope3576+ 36, // 24: vev.wire.v1.BrokerServerEnvelope.preview_publication:type_name -> vev.wire.v1.PreviewPublication
3483- 0, // 25: vev.wire.v1.Resync.scope:type_name -> vev.wire.v1.BrokerScope3577+ 18, // 25: vev.wire.v1.BrokerServerEnvelope.stream_window_update:type_name -> vev.wire.v1.StreamWindowUpdate
3484- 0, // 26: vev.wire.v1.Unsubscribe.scope:type_name -> vev.wire.v1.BrokerScope3578+ 0, // 26: vev.wire.v1.Subscribe.scope:type_name -> vev.wire.v1.BrokerScope
3485- 0, // 27: vev.wire.v1.AddHost.scope:type_name -> vev.wire.v1.BrokerScope3579+ 0, // 27: vev.wire.v1.Resync.scope:type_name -> vev.wire.v1.BrokerScope
3486- 2, // 28: vev.wire.v1.AddHost.policy:type_name -> vev.wire.v1.BrokerWirePolicy3580+ 0, // 28: vev.wire.v1.Unsubscribe.scope:type_name -> vev.wire.v1.BrokerScope
3487- 0, // 29: vev.wire.v1.RemoveHost.scope:type_name -> vev.wire.v1.BrokerScope3581+ 0, // 29: vev.wire.v1.AddHost.scope:type_name -> vev.wire.v1.BrokerScope
3488- 38, // 30: vev.wire.v1.RemoveHost.registration:type_name -> vev.wire.v1.RemoteRegistration3582+ 2, // 30: vev.wire.v1.AddHost.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3489- 0, // 31: vev.wire.v1.UpdateHostPolicy.scope:type_name -> vev.wire.v1.BrokerScope3583+ 0, // 31: vev.wire.v1.RemoveHost.scope:type_name -> vev.wire.v1.BrokerScope
3490- 38, // 32: vev.wire.v1.UpdateHostPolicy.registration:type_name -> vev.wire.v1.RemoteRegistration3584+ 39, // 32: vev.wire.v1.RemoveHost.registration:type_name -> vev.wire.v1.RemoteRegistration
3491- 2, // 33: vev.wire.v1.UpdateHostPolicy.policy:type_name -> vev.wire.v1.BrokerWirePolicy3585+ 0, // 33: vev.wire.v1.UpdateHostPolicy.scope:type_name -> vev.wire.v1.BrokerScope
3492- 0, // 34: vev.wire.v1.Reconcile.scope:type_name -> vev.wire.v1.BrokerScope3586+ 39, // 34: vev.wire.v1.UpdateHostPolicy.registration:type_name -> vev.wire.v1.RemoteRegistration
3493- 38, // 35: vev.wire.v1.Reconcile.registration:type_name -> vev.wire.v1.RemoteRegistration3587+ 2, // 35: vev.wire.v1.UpdateHostPolicy.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3494- 1, // 36: vev.wire.v1.OpenStream.ref:type_name -> vev.wire.v1.BrokerStreamRef3588+ 0, // 36: vev.wire.v1.Reconcile.scope:type_name -> vev.wire.v1.BrokerScope
3495- 38, // 37: vev.wire.v1.OpenStream.registration:type_name -> vev.wire.v1.RemoteRegistration3589+ 39, // 37: vev.wire.v1.Reconcile.registration:type_name -> vev.wire.v1.RemoteRegistration
3496- 39, // 38: vev.wire.v1.OpenStream.target:type_name -> vev.wire.v1.ExactTarget3590+ 1, // 38: vev.wire.v1.OpenStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3497- 2, // 39: vev.wire.v1.OpenStream.policy:type_name -> vev.wire.v1.BrokerWirePolicy3591+ 39, // 39: vev.wire.v1.OpenStream.registration:type_name -> vev.wire.v1.RemoteRegistration
3498- 1, // 40: vev.wire.v1.ClientStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef3592+ 40, // 40: vev.wire.v1.OpenStream.target:type_name -> vev.wire.v1.ExactTarget
3499- 1, // 41: vev.wire.v1.CloseStream.ref:type_name -> vev.wire.v1.BrokerStreamRef3593+ 2, // 41: vev.wire.v1.OpenStream.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3500- 0, // 42: vev.wire.v1.Registered.scope:type_name -> vev.wire.v1.BrokerScope3594+ 1, // 42: vev.wire.v1.ClientStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3501- 0, // 43: vev.wire.v1.SnapshotPart.scope:type_name -> vev.wire.v1.BrokerScope3595+ 1, // 43: vev.wire.v1.CloseStream.ref:type_name -> vev.wire.v1.BrokerStreamRef
3502- 20, // 44: vev.wire.v1.SnapshotPart.begin:type_name -> vev.wire.v1.SnapshotBegin3596+ 1, // 44: vev.wire.v1.StreamWindowUpdate.ref:type_name -> vev.wire.v1.BrokerStreamRef
3503- 21, // 45: vev.wire.v1.SnapshotPart.daemon:type_name -> vev.wire.v1.SnapshotDaemon3597+ 0, // 45: vev.wire.v1.Registered.scope:type_name -> vev.wire.v1.BrokerScope
3504- 22, // 46: vev.wire.v1.SnapshotPart.session:type_name -> vev.wire.v1.SnapshotSession3598+ 0, // 46: vev.wire.v1.SnapshotPart.scope:type_name -> vev.wire.v1.BrokerScope
3505- 23, // 47: vev.wire.v1.SnapshotPart.tombstone:type_name -> vev.wire.v1.SnapshotTombstone3599+ 21, // 47: vev.wire.v1.SnapshotPart.begin:type_name -> vev.wire.v1.SnapshotBegin
3506- 24, // 48: vev.wire.v1.SnapshotPart.end:type_name -> vev.wire.v1.SnapshotEnd3600+ 22, // 48: vev.wire.v1.SnapshotPart.daemon:type_name -> vev.wire.v1.SnapshotDaemon
3507- 38, // 49: vev.wire.v1.SnapshotDaemon.registration:type_name -> vev.wire.v1.RemoteRegistration3601+ 23, // 49: vev.wire.v1.SnapshotPart.session:type_name -> vev.wire.v1.SnapshotSession
3508- 2, // 50: vev.wire.v1.SnapshotDaemon.policy:type_name -> vev.wire.v1.BrokerWirePolicy3602+ 24, // 50: vev.wire.v1.SnapshotPart.tombstone:type_name -> vev.wire.v1.SnapshotTombstone
3509- 4, // 51: vev.wire.v1.SnapshotDaemon.last_attempt:type_name -> vev.wire.v1.BrokerTimestamp3603+ 25, // 51: vev.wire.v1.SnapshotPart.end:type_name -> vev.wire.v1.SnapshotEnd
3510- 4, // 52: vev.wire.v1.SnapshotDaemon.last_success:type_name -> vev.wire.v1.BrokerTimestamp3604+ 39, // 52: vev.wire.v1.SnapshotDaemon.registration:type_name -> vev.wire.v1.RemoteRegistration
3511- 4, // 53: vev.wire.v1.SnapshotDaemon.next_due:type_name -> vev.wire.v1.BrokerTimestamp3605+ 2, // 53: vev.wire.v1.SnapshotDaemon.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3512- 36, // 54: vev.wire.v1.SnapshotSession.session:type_name -> vev.wire.v1.BrokerCatalogSession3606+ 4, // 54: vev.wire.v1.SnapshotDaemon.last_attempt:type_name -> vev.wire.v1.BrokerTimestamp
3513- 38, // 55: vev.wire.v1.SnapshotTombstone.registration:type_name -> vev.wire.v1.RemoteRegistration3607+ 4, // 55: vev.wire.v1.SnapshotDaemon.last_success:type_name -> vev.wire.v1.BrokerTimestamp
3514- 0, // 56: vev.wire.v1.OperationResult.scope:type_name -> vev.wire.v1.BrokerScope3608+ 4, // 56: vev.wire.v1.SnapshotDaemon.next_due:type_name -> vev.wire.v1.BrokerTimestamp
3515- 3, // 57: vev.wire.v1.OperationResult.error:type_name -> vev.wire.v1.BrokerErrorDetail3609+ 37, // 57: vev.wire.v1.SnapshotSession.session:type_name -> vev.wire.v1.BrokerCatalogSession
3516- 38, // 58: vev.wire.v1.OperationResult.registration:type_name -> vev.wire.v1.RemoteRegistration3610+ 39, // 58: vev.wire.v1.SnapshotTombstone.registration:type_name -> vev.wire.v1.RemoteRegistration
3517- 1, // 59: vev.wire.v1.StreamOpened.ref:type_name -> vev.wire.v1.BrokerStreamRef3611+ 0, // 59: vev.wire.v1.OperationResult.scope:type_name -> vev.wire.v1.BrokerScope
3518- 1, // 60: vev.wire.v1.ServerStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef3612+ 3, // 60: vev.wire.v1.OperationResult.error:type_name -> vev.wire.v1.BrokerErrorDetail
3519- 1, // 61: vev.wire.v1.StreamClosed.ref:type_name -> vev.wire.v1.BrokerStreamRef3613+ 39, // 61: vev.wire.v1.OperationResult.registration:type_name -> vev.wire.v1.RemoteRegistration
3520- 3, // 62: vev.wire.v1.StreamClosed.error:type_name -> vev.wire.v1.BrokerErrorDetail3614+ 1, // 62: vev.wire.v1.StreamOpened.ref:type_name -> vev.wire.v1.BrokerStreamRef
3521- 1, // 63: vev.wire.v1.Progress.ref:type_name -> vev.wire.v1.BrokerStreamRef3615+ 1, // 63: vev.wire.v1.ServerStreamData.ref:type_name -> vev.wire.v1.BrokerStreamRef
3522- 0, // 64: vev.wire.v1.BrokerErrorMessage.scope:type_name -> vev.wire.v1.BrokerScope3616+ 1, // 64: vev.wire.v1.StreamClosed.ref:type_name -> vev.wire.v1.BrokerStreamRef
3523- 3, // 65: vev.wire.v1.BrokerErrorMessage.error:type_name -> vev.wire.v1.BrokerErrorDetail3617+ 3, // 65: vev.wire.v1.StreamClosed.error:type_name -> vev.wire.v1.BrokerErrorDetail
3524- 0, // 66: vev.wire.v1.Shutdown.scope:type_name -> vev.wire.v1.BrokerScope3618+ 1, // 66: vev.wire.v1.Progress.ref:type_name -> vev.wire.v1.BrokerStreamRef
3525- 38, // 67: vev.wire.v1.PreviewRoute.registration:type_name -> vev.wire.v1.RemoteRegistration3619+ 0, // 67: vev.wire.v1.BrokerErrorMessage.scope:type_name -> vev.wire.v1.BrokerScope
3526- 2, // 68: vev.wire.v1.PreviewRoute.policy:type_name -> vev.wire.v1.BrokerWirePolicy3620+ 3, // 68: vev.wire.v1.BrokerErrorMessage.error:type_name -> vev.wire.v1.BrokerErrorDetail
3527- 0, // 69: vev.wire.v1.StartPreview.scope:type_name -> vev.wire.v1.BrokerScope3621+ 0, // 69: vev.wire.v1.Shutdown.scope:type_name -> vev.wire.v1.BrokerScope
3528- 40, // 70: vev.wire.v1.StartPreview.preview:type_name -> vev.wire.v1.RemotePreviewRequest3622+ 39, // 70: vev.wire.v1.PreviewRoute.registration:type_name -> vev.wire.v1.RemoteRegistration
3529- 32, // 71: vev.wire.v1.StartPreview.route:type_name -> vev.wire.v1.PreviewRoute3623+ 2, // 71: vev.wire.v1.PreviewRoute.policy:type_name -> vev.wire.v1.BrokerWirePolicy
3530- 0, // 72: vev.wire.v1.CancelPreview.scope:type_name -> vev.wire.v1.BrokerScope3624+ 0, // 72: vev.wire.v1.StartPreview.scope:type_name -> vev.wire.v1.BrokerScope
3531- 0, // 73: vev.wire.v1.PreviewPublication.scope:type_name -> vev.wire.v1.BrokerScope3625+ 41, // 73: vev.wire.v1.StartPreview.preview:type_name -> vev.wire.v1.RemotePreviewRequest
3532- 41, // 74: vev.wire.v1.PreviewPublication.preview:type_name -> vev.wire.v1.RemotePreview3626+ 33, // 74: vev.wire.v1.StartPreview.route:type_name -> vev.wire.v1.PreviewRoute
3533- 3, // 75: vev.wire.v1.PreviewPublication.error:type_name -> vev.wire.v1.BrokerErrorDetail3627+ 0, // 75: vev.wire.v1.CancelPreview.scope:type_name -> vev.wire.v1.BrokerScope
3534- 37, // 76: vev.wire.v1.BrokerCatalogSession.tabs:type_name -> vev.wire.v1.BrokerCatalogTab3628+ 0, // 76: vev.wire.v1.PreviewPublication.scope:type_name -> vev.wire.v1.BrokerScope
3535- 77, // [77:77] is the sub-list for method output_type3629+ 42, // 77: vev.wire.v1.PreviewPublication.preview:type_name -> vev.wire.v1.RemotePreview
3536- 77, // [77:77] is the sub-list for method input_type3630+ 3, // 78: vev.wire.v1.PreviewPublication.error:type_name -> vev.wire.v1.BrokerErrorDetail
3537- 77, // [77:77] is the sub-list for extension type_name3631+ 38, // 79: vev.wire.v1.BrokerCatalogSession.tabs:type_name -> vev.wire.v1.BrokerCatalogTab
3538- 77, // [77:77] is the sub-list for extension extendee3632+ 80, // [80:80] is the sub-list for method output_type
3539- 0, // [0:77] is the sub-list for field type_name3633+ 80, // [80:80] is the sub-list for method input_type
3634+ 80, // [80:80] is the sub-list for extension type_name
3635+ 80, // [80:80] is the sub-list for extension extendee
3636+ 0, // [0:80] is the sub-list for field type_name
3540 }3637 }
3541 3638
3542 func init() { file_broker_proto_init() }3639 func init() { file_broker_proto_init() }
@@ -3560,6 +3657,7 @@ func file_broker_proto_init() {
3560 (*BrokerClientEnvelope_UpdateHostPolicy)(nil),3657 (*BrokerClientEnvelope_UpdateHostPolicy)(nil),
3561 (*BrokerClientEnvelope_StartPreview)(nil),3658 (*BrokerClientEnvelope_StartPreview)(nil),
3562 (*BrokerClientEnvelope_CancelPreview)(nil),3659 (*BrokerClientEnvelope_CancelPreview)(nil),
3660+ (*BrokerClientEnvelope_StreamWindowUpdate)(nil),
3563 }3661 }
3564 file_broker_proto_msgTypes[6].OneofWrappers = []any{3662 file_broker_proto_msgTypes[6].OneofWrappers = []any{
3565 (*BrokerServerEnvelope_Registered)(nil),3663 (*BrokerServerEnvelope_Registered)(nil),
@@ -3572,15 +3670,16 @@ func file_broker_proto_init() {
3572 (*BrokerServerEnvelope_BrokerErrorMessage)(nil),3670 (*BrokerServerEnvelope_BrokerErrorMessage)(nil),
3573 (*BrokerServerEnvelope_Shutdown)(nil),3671 (*BrokerServerEnvelope_Shutdown)(nil),
3574 (*BrokerServerEnvelope_PreviewPublication)(nil),3672 (*BrokerServerEnvelope_PreviewPublication)(nil),
3673+ (*BrokerServerEnvelope_StreamWindowUpdate)(nil),
3575 }3674 }
3576- file_broker_proto_msgTypes[19].OneofWrappers = []any{3675+ file_broker_proto_msgTypes[20].OneofWrappers = []any{
3577 (*SnapshotPart_Begin)(nil),3676 (*SnapshotPart_Begin)(nil),
3578 (*SnapshotPart_Daemon)(nil),3677 (*SnapshotPart_Daemon)(nil),
3579 (*SnapshotPart_Session)(nil),3678 (*SnapshotPart_Session)(nil),
3580 (*SnapshotPart_Tombstone)(nil),3679 (*SnapshotPart_Tombstone)(nil),
3581 (*SnapshotPart_End)(nil),3680 (*SnapshotPart_End)(nil),
3582 }3681 }
3583- file_broker_proto_msgTypes[35].OneofWrappers = []any{3682+ file_broker_proto_msgTypes[36].OneofWrappers = []any{
3584 (*PreviewPublication_Preview)(nil),3683 (*PreviewPublication_Preview)(nil),
3585 (*PreviewPublication_Error)(nil),3684 (*PreviewPublication_Error)(nil),
3586 }3685 }
@@ -3590,7 +3689,7 @@ func file_broker_proto_init() {
3590 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),3689 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
3591 RawDescriptor: unsafe.Slice(unsafe.StringData(file_broker_proto_rawDesc), len(file_broker_proto_rawDesc)),3690 RawDescriptor: unsafe.Slice(unsafe.StringData(file_broker_proto_rawDesc), len(file_broker_proto_rawDesc)),
3592 NumEnums: 0,3691 NumEnums: 0,
3593- NumMessages: 38,3692+ NumMessages: 39,
3594 NumExtensions: 0,3693 NumExtensions: 0,
3595 NumServices: 0,3694 NumServices: 0,
3596 },3695 },
M internal/protocol/wire/broker_test.go +8 -2
@@ -20,7 +20,7 @@ var brokerClientTags = map[string]protoreflect.FieldNumber{
2020 "register": 101, "subscribe": 102, "resync": 103, "unsubscribe": 104,
2121 "add_host": 105, "remove_host": 106, "reconcile": 107, "open_stream": 108,
2222 "client_stream_data": 109, "close_stream": 110, "update_host_policy": 111,
23- "start_preview": 112, "cancel_preview": 113,
23+ "start_preview": 112, "cancel_preview": 113, "stream_window_update": 114,
2424 }
2525
2626 // brokerServerTags is the frozen server tag inventory.
@@ -28,7 +28,7 @@ var brokerServerTags = map[string]protoreflect.FieldNumber{
2828 "registered": 201, "snapshot_part": 202, "operation_result": 203,
2929 "stream_opened": 204, "server_stream_data": 205, "stream_closed": 206,
3030 "progress": 207, "broker_error_message": 208, "shutdown": 209,
31- "preview_publication": 210,
31+ "preview_publication": 210, "stream_window_update": 211,
3232 }
3333
3434 func oneofTags(t *testing.T, message proto.Message) map[string]protoreflect.FieldNumber {
@@ -194,6 +194,9 @@ func brokerClientSamples() map[string]*BrokerClientEnvelope {
194194 "cancel_preview": {Payload: &BrokerClientEnvelope_CancelPreview{CancelPreview: &CancelPreview{
195195 Scope: brokerScope(), Generation: 3,
196196 }}},
197+ "stream_window_update": {Payload: &BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: &StreamWindowUpdate{
198+ Ref: brokerRef(), Credit: 4096,
199+ }}},
197200 }
198201 }
199202
@@ -241,6 +244,9 @@ func brokerServerSamples() map[string]*BrokerServerEnvelope {
241244 Scope: brokerScope(), Generation: 3,
242245 Result: &PreviewPublication_Error{Error: &BrokerErrorDetail{Code: 1, Text: "unavailable"}},
243246 }}},
247+ "stream_window_update": {Payload: &BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: &StreamWindowUpdate{
248+ Ref: brokerRef(), Credit: 4096,
249+ }}},
244250 }
245251 }
246252
@@ -20,7 +20,7 @@ var brokerClientTags = map[string]protoreflect.FieldNumber{
20 "register": 101, "subscribe": 102, "resync": 103, "unsubscribe": 104,20 "register": 101, "subscribe": 102, "resync": 103, "unsubscribe": 104,
21 "add_host": 105, "remove_host": 106, "reconcile": 107, "open_stream": 108,21 "add_host": 105, "remove_host": 106, "reconcile": 107, "open_stream": 108,
22 "client_stream_data": 109, "close_stream": 110, "update_host_policy": 111,22 "client_stream_data": 109, "close_stream": 110, "update_host_policy": 111,
23- "start_preview": 112, "cancel_preview": 113,23+ "start_preview": 112, "cancel_preview": 113, "stream_window_update": 114,
24 }24 }
25 25
26 // brokerServerTags is the frozen server tag inventory.26 // brokerServerTags is the frozen server tag inventory.
@@ -28,7 +28,7 @@ var brokerServerTags = map[string]protoreflect.FieldNumber{
28 "registered": 201, "snapshot_part": 202, "operation_result": 203,28 "registered": 201, "snapshot_part": 202, "operation_result": 203,
29 "stream_opened": 204, "server_stream_data": 205, "stream_closed": 206,29 "stream_opened": 204, "server_stream_data": 205, "stream_closed": 206,
30 "progress": 207, "broker_error_message": 208, "shutdown": 209,30 "progress": 207, "broker_error_message": 208, "shutdown": 209,
31- "preview_publication": 210,31+ "preview_publication": 210, "stream_window_update": 211,
32 }32 }
33 33
34 func oneofTags(t *testing.T, message proto.Message) map[string]protoreflect.FieldNumber {34 func oneofTags(t *testing.T, message proto.Message) map[string]protoreflect.FieldNumber {
@@ -194,6 +194,9 @@ func brokerClientSamples() map[string]*BrokerClientEnvelope {
194 "cancel_preview": {Payload: &BrokerClientEnvelope_CancelPreview{CancelPreview: &CancelPreview{194 "cancel_preview": {Payload: &BrokerClientEnvelope_CancelPreview{CancelPreview: &CancelPreview{
195 Scope: brokerScope(), Generation: 3,195 Scope: brokerScope(), Generation: 3,
196 }}},196 }}},
197+ "stream_window_update": {Payload: &BrokerClientEnvelope_StreamWindowUpdate{StreamWindowUpdate: &StreamWindowUpdate{
198+ Ref: brokerRef(), Credit: 4096,
199+ }}},
197 }200 }
198 }201 }
199 202
@@ -241,6 +244,9 @@ func brokerServerSamples() map[string]*BrokerServerEnvelope {
241 Scope: brokerScope(), Generation: 3,244 Scope: brokerScope(), Generation: 3,
242 Result: &PreviewPublication_Error{Error: &BrokerErrorDetail{Code: 1, Text: "unavailable"}},245 Result: &PreviewPublication_Error{Error: &BrokerErrorDetail{Code: 1, Text: "unavailable"}},
243 }}},246 }}},
247+ "stream_window_update": {Payload: &BrokerServerEnvelope_StreamWindowUpdate{StreamWindowUpdate: &StreamWindowUpdate{
248+ Ref: brokerRef(), Credit: 4096,
249+ }}},
244 }250 }
245 }251 }
246 252
M internal/protocol/wire/envelope_inventory_test.go +2 -2
@@ -15,8 +15,8 @@ import (
1515 // shares a payload type except route navigation failure, and the preamble
1616 // constants agree with the schema authority.
1717 func TestEnvelopeInventoryProvesDirectionAndSemanticPath(t *testing.T) {
18- if protocol.Version != 65 {
19- t.Fatalf("protocol.Version = %d, want 65", protocol.Version)
18+ if protocol.Version != 66 {
19+ t.Fatalf("protocol.Version = %d, want 66", protocol.Version)
2020 }
2121 if ProtocolEpoch != 1 {
2222 t.Fatalf("ProtocolEpoch = %d, want 1", ProtocolEpoch)
@@ -15,8 +15,8 @@ import (
15 // shares a payload type except route navigation failure, and the preamble15 // shares a payload type except route navigation failure, and the preamble
16 // constants agree with the schema authority.16 // constants agree with the schema authority.
17 func TestEnvelopeInventoryProvesDirectionAndSemanticPath(t *testing.T) {17 func TestEnvelopeInventoryProvesDirectionAndSemanticPath(t *testing.T) {
18- if protocol.Version != 65 {18+ if protocol.Version != 66 {
19- t.Fatalf("protocol.Version = %d, want 65", protocol.Version)19+ t.Fatalf("protocol.Version = %d, want 66", protocol.Version)
20 }20 }
21 if ProtocolEpoch != 1 {21 if ProtocolEpoch != 1 {
22 t.Fatalf("ProtocolEpoch = %d, want 1", ProtocolEpoch)22 t.Fatalf("ProtocolEpoch = %d, want 1", ProtocolEpoch)
M internal/protocol/wire/schema/broker.proto +11 -0
@@ -89,6 +89,7 @@ message BrokerClientEnvelope {
8989 UpdateHostPolicy update_host_policy = 111;
9090 StartPreview start_preview = 112;
9191 CancelPreview cancel_preview = 113;
92+ StreamWindowUpdate stream_window_update = 114;
9293 }
9394 }
9495
@@ -106,6 +107,7 @@ message BrokerServerEnvelope {
106107 BrokerErrorMessage broker_error_message = 208;
107108 Shutdown shutdown = 209;
108109 PreviewPublication preview_publication = 210;
110+ StreamWindowUpdate stream_window_update = 211;
109111 }
110112 }
111113
@@ -221,6 +223,15 @@ message CloseStream {
221223 BrokerStreamRef ref = 1;
222224 }
223225
226+// StreamWindowUpdate grants the peer credit to send more stream data on one
227+// logical stream: the receiver consumed that much of its stream window. Each
228+// direction of a stream has its own window, so a client grants credit for
229+// ServerStreamData and the server grants credit for ClientStreamData.
230+message StreamWindowUpdate {
231+ BrokerStreamRef ref = 1;
232+ uint64 credit = 2;
233+}
234+
224235 // Registered confirms one accepted client connection and its assigned scope.
225236 message Registered {
226237 BrokerScope scope = 1;
@@ -89,6 +89,7 @@ message BrokerClientEnvelope {
89 UpdateHostPolicy update_host_policy = 111;89 UpdateHostPolicy update_host_policy = 111;
90 StartPreview start_preview = 112;90 StartPreview start_preview = 112;
91 CancelPreview cancel_preview = 113;91 CancelPreview cancel_preview = 113;
92+ StreamWindowUpdate stream_window_update = 114;
92 }93 }
93 }94 }
94 95
@@ -106,6 +107,7 @@ message BrokerServerEnvelope {
106 BrokerErrorMessage broker_error_message = 208;107 BrokerErrorMessage broker_error_message = 208;
107 Shutdown shutdown = 209;108 Shutdown shutdown = 209;
108 PreviewPublication preview_publication = 210;109 PreviewPublication preview_publication = 210;
110+ StreamWindowUpdate stream_window_update = 211;
109 }111 }
110 }112 }
111 113
@@ -221,6 +223,15 @@ message CloseStream {
221 BrokerStreamRef ref = 1;223 BrokerStreamRef ref = 1;
222 }224 }
223 225
226+// StreamWindowUpdate grants the peer credit to send more stream data on one
227+// logical stream: the receiver consumed that much of its stream window. Each
228+// direction of a stream has its own window, so a client grants credit for
229+// ServerStreamData and the server grants credit for ClientStreamData.
230+message StreamWindowUpdate {
231+ BrokerStreamRef ref = 1;
232+ uint64 credit = 2;
233+}
234+
224 // Registered confirms one accepted client connection and its assigned scope.235 // Registered confirms one accepted client connection and its assigned scope.
225 message Registered {236 message Registered {
226 BrokerScope scope = 1;237 BrokerScope scope = 1;
M internal/usecase/daemon/attachment_output.go +86 -9
@@ -23,14 +23,22 @@ import (
2323 // Callers serialize access with attachedClient.sendMu. A prepared transaction
2424 // must be sent or committed as a no-op before the next transaction is prepared.
2525 type attachmentOutput struct {
26- renderer *renderer.Renderer
27- epoch uint64
28- next uint64
29- acked uint64
30- generation uint64
31- maxOutstanding uint64
32- outstandingAtomic atomic.Uint64
33- maxOutstandingAtomic atomic.Uint64
26+ renderer *renderer.Renderer
27+ epoch uint64
28+ next uint64
29+ acked uint64
30+ generation uint64
31+ maxOutstanding uint64
32+ outstandingAtomic atomic.Uint64
33+ maxOutstandingAtomic atomic.Uint64
34+ // unackedBytes charges Output data sent and not yet acknowledged, keyed by
35+ // the state an ACK must reach to release it. Side-effect output carries no
36+ // state of its own, so it is charged to the next state. Charges stay in
37+ // non-decreasing state order and merge per state, so the slice holds at most
38+ // maxOutstanding+1 entries. Guarded by the view lock; unackedBytesAtomic
39+ // mirrors the total for lock-free capacity reads.
40+ unackedBytes []unackedOutputBytes
41+ unackedBytesAtomic atomic.Uint64
3442 forceSnapshot bool
3543 initialized bool
3644 viewPublication uint64
@@ -43,6 +51,51 @@ type attachmentOutput struct {
4351 stateMu sync.Mutex
4452 }
4553
54+// unackedOutputBytes is the Output data released once the client acknowledges
55+// state.
56+type unackedOutputBytes struct {
57+ state uint64
58+ bytes uint64
59+}
60+
61+// chargeBytesLocked records n bytes released once state is acknowledged.
62+// Caller holds the view lock.
63+func (s *attachmentOutput) chargeBytesLocked(state uint64, n int) {
64+ if n <= 0 {
65+ return
66+ }
67+ if last := len(s.unackedBytes) - 1; last >= 0 && s.unackedBytes[last].state == state {
68+ s.unackedBytes[last].bytes += uint64(n)
69+ } else {
70+ s.unackedBytes = append(s.unackedBytes, unackedOutputBytes{state: state, bytes: uint64(n)})
71+ }
72+ s.unackedBytesAtomic.Add(uint64(n))
73+}
74+
75+// releaseBytesLocked drops every charge an acknowledgement of state covers.
76+// Caller holds the view lock.
77+func (s *attachmentOutput) releaseBytesLocked(state uint64) {
78+ released, kept := uint64(0), 0
79+ for _, charge := range s.unackedBytes {
80+ if charge.state <= state {
81+ released += charge.bytes
82+ continue
83+ }
84+ s.unackedBytes[kept] = charge
85+ kept++
86+ }
87+ clear(s.unackedBytes[kept:])
88+ s.unackedBytes = s.unackedBytes[:kept]
89+ s.unackedBytesAtomic.Add(-released)
90+}
91+
92+// resetBytesLocked forgets every charge: a new epoch acknowledges nothing the
93+// old one sent. Caller holds the view lock.
94+func (s *attachmentOutput) resetBytesLocked() {
95+ s.unackedBytes = s.unackedBytes[:0]
96+ s.unackedBytesAtomic.Store(0)
97+}
98+
4699 // reconfigureAttachmentOutput applies a replacement terminal's declared
47100 // capabilities to attachment-owned output state. Caller holds d.mu.
48101 func (d *Daemon) reconfigureAttachmentOutput(sess *session, ac *attachedClient, h protocol.Hello) {
@@ -169,6 +222,9 @@ func (o *attachmentOutput) prepareFrame(d *Daemon, state *capturedRenderState, f
169222 ansi.viewRevision = state.view.revision
170223 cursor := o.prepareCursorTail(desired, len(ansi.data) > 0)
171224 graphicsReset := reset || o.forceSnapshot || !o.initialized
225+ // Images share the frame's data limit. The unacknowledged byte window gates
226+ // the next frame rather than this one: an upload must fit one frame, so
227+ // clamping it here would drop large images and their deletes outright.
172228 graphicsBudget := max(protocol.MaxOutputDataLen-len(ansi.data)-len(cursor.data), 0)
173229 preparedGraphics, err := graphicsOutputDataWithDaemonLimit(d, state, o.attachment, graphicsReset, graphicsBudget)
174230 if err != nil {
@@ -374,6 +430,7 @@ func (p *preparedOutput) send(data []byte, echoAck uint64, send func(protocol.Ou
374430 p.boundary = protocol.UIReceipt{Epoch: p.epoch, State: p.next, ViewPublication: context.Publication, Outcome: protocol.UIReceiptProcessed}
375431 }
376432 p.stream.next = p.next
433+ p.stream.chargeBytesLocked(p.next, len(data))
377434 p.stream.publishOutstanding()
378435 p.sent = true
379436 return nil
@@ -510,6 +567,17 @@ func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (protoc
510567 return marshalOutputState(data, epoch, 0, 0, echoAck, viewRevision, size, false, nil)
511568 }
512569
570+// sideEffectSentLocked charges a side effect that reached the transport. It has
571+// no state of its own, so the ACK of the next state frame releases it: every
572+// state frame is sent after it on the same ordered stream. Caller holds the view
573+// lock that built output, so the epoch cannot have moved.
574+func (s *attachmentOutput) sideEffectSentLocked(output protocol.Output) {
575+ if s == nil || output.Epoch != s.currentEpochLocked() {
576+ return
577+ }
578+ s.chargeBytesLocked(s.next+1, len(output.Data))
579+}
580+
513581 func (s *attachmentOutput) ack(epoch, state uint64) bool {
514582 if s == nil {
515583 return false
@@ -520,6 +588,7 @@ func (s *attachmentOutput) ack(epoch, state uint64) bool {
520588 return false
521589 }
522590 s.acked = state
591+ s.releaseBytesLocked(state)
523592 s.publishOutstanding()
524593 return true
525594 }
@@ -556,6 +625,7 @@ func (s *attachmentOutput) rebaseLocked() {
556625 s.epoch = epoch + 1
557626 s.next = 0
558627 s.acked = 0
628+ s.resetBytesLocked()
559629 s.generation++
560630 s.publishOutstanding()
561631 s.maxOutstandingAtomic.Store(s.maxOutstanding)
@@ -591,6 +661,11 @@ func (s *attachmentOutput) setWindow(window uint8) {
591661 s.maxOutstandingAtomic.Store(s.maxOutstanding)
592662 }
593663
664+// atCapacity reports whether the next state frame must wait for an ACK: the
665+// frame window is full, or unacknowledged Output data reached
666+// protocol.MaxOutputWindowBytes while a state frame is in flight. With no state
667+// frame in flight the next frame is always allowed, since only its ACK can
668+// release bytes charged by side effects.
594669 func (s *attachmentOutput) atCapacity() bool {
595670 if s == nil {
596671 return false
@@ -599,7 +674,9 @@ func (s *attachmentOutput) atCapacity() bool {
599674 if maxOutstanding == 0 {
600675 maxOutstanding = s.maxOutstanding
601676 }
602- return s.outstandingAtomic.Load() >= maxOutstanding
677+ outstanding := s.outstandingAtomic.Load()
678+ return outstanding >= maxOutstanding ||
679+ (outstanding > 0 && s.unackedBytesAtomic.Load() >= protocol.MaxOutputWindowBytes)
603680 }
604681
605682 // committedState reports the latest committed output state number. It takes
@@ -23,14 +23,22 @@ import (
23 // Callers serialize access with attachedClient.sendMu. A prepared transaction23 // Callers serialize access with attachedClient.sendMu. A prepared transaction
24 // must be sent or committed as a no-op before the next transaction is prepared.24 // must be sent or committed as a no-op before the next transaction is prepared.
25 type attachmentOutput struct {25 type attachmentOutput struct {
26- renderer *renderer.Renderer26+ renderer *renderer.Renderer
27- epoch uint6427+ epoch uint64
28- next uint6428+ next uint64
29- acked uint6429+ acked uint64
30- generation uint6430+ generation uint64
31- maxOutstanding uint6431+ maxOutstanding uint64
32- outstandingAtomic atomic.Uint6432+ outstandingAtomic atomic.Uint64
33- maxOutstandingAtomic atomic.Uint6433+ maxOutstandingAtomic atomic.Uint64
34+ // unackedBytes charges Output data sent and not yet acknowledged, keyed by
35+ // the state an ACK must reach to release it. Side-effect output carries no
36+ // state of its own, so it is charged to the next state. Charges stay in
37+ // non-decreasing state order and merge per state, so the slice holds at most
38+ // maxOutstanding+1 entries. Guarded by the view lock; unackedBytesAtomic
39+ // mirrors the total for lock-free capacity reads.
40+ unackedBytes []unackedOutputBytes
41+ unackedBytesAtomic atomic.Uint64
34 forceSnapshot bool42 forceSnapshot bool
35 initialized bool43 initialized bool
36 viewPublication uint6444 viewPublication uint64
@@ -43,6 +51,51 @@ type attachmentOutput struct {
43 stateMu sync.Mutex51 stateMu sync.Mutex
44 }52 }
45 53
54+// unackedOutputBytes is the Output data released once the client acknowledges
55+// state.
56+type unackedOutputBytes struct {
57+ state uint64
58+ bytes uint64
59+}
60+
61+// chargeBytesLocked records n bytes released once state is acknowledged.
62+// Caller holds the view lock.
63+func (s *attachmentOutput) chargeBytesLocked(state uint64, n int) {
64+ if n <= 0 {
65+ return
66+ }
67+ if last := len(s.unackedBytes) - 1; last >= 0 && s.unackedBytes[last].state == state {
68+ s.unackedBytes[last].bytes += uint64(n)
69+ } else {
70+ s.unackedBytes = append(s.unackedBytes, unackedOutputBytes{state: state, bytes: uint64(n)})
71+ }
72+ s.unackedBytesAtomic.Add(uint64(n))
73+}
74+
75+// releaseBytesLocked drops every charge an acknowledgement of state covers.
76+// Caller holds the view lock.
77+func (s *attachmentOutput) releaseBytesLocked(state uint64) {
78+ released, kept := uint64(0), 0
79+ for _, charge := range s.unackedBytes {
80+ if charge.state <= state {
81+ released += charge.bytes
82+ continue
83+ }
84+ s.unackedBytes[kept] = charge
85+ kept++
86+ }
87+ clear(s.unackedBytes[kept:])
88+ s.unackedBytes = s.unackedBytes[:kept]
89+ s.unackedBytesAtomic.Add(-released)
90+}
91+
92+// resetBytesLocked forgets every charge: a new epoch acknowledges nothing the
93+// old one sent. Caller holds the view lock.
94+func (s *attachmentOutput) resetBytesLocked() {
95+ s.unackedBytes = s.unackedBytes[:0]
96+ s.unackedBytesAtomic.Store(0)
97+}
98+
46 // reconfigureAttachmentOutput applies a replacement terminal's declared99 // reconfigureAttachmentOutput applies a replacement terminal's declared
47 // capabilities to attachment-owned output state. Caller holds d.mu.100 // capabilities to attachment-owned output state. Caller holds d.mu.
48 func (d *Daemon) reconfigureAttachmentOutput(sess *session, ac *attachedClient, h protocol.Hello) {101 func (d *Daemon) reconfigureAttachmentOutput(sess *session, ac *attachedClient, h protocol.Hello) {
@@ -169,6 +222,9 @@ func (o *attachmentOutput) prepareFrame(d *Daemon, state *capturedRenderState, f
169 ansi.viewRevision = state.view.revision222 ansi.viewRevision = state.view.revision
170 cursor := o.prepareCursorTail(desired, len(ansi.data) > 0)223 cursor := o.prepareCursorTail(desired, len(ansi.data) > 0)
171 graphicsReset := reset || o.forceSnapshot || !o.initialized224 graphicsReset := reset || o.forceSnapshot || !o.initialized
225+ // Images share the frame's data limit. The unacknowledged byte window gates
226+ // the next frame rather than this one: an upload must fit one frame, so
227+ // clamping it here would drop large images and their deletes outright.
172 graphicsBudget := max(protocol.MaxOutputDataLen-len(ansi.data)-len(cursor.data), 0)228 graphicsBudget := max(protocol.MaxOutputDataLen-len(ansi.data)-len(cursor.data), 0)
173 preparedGraphics, err := graphicsOutputDataWithDaemonLimit(d, state, o.attachment, graphicsReset, graphicsBudget)229 preparedGraphics, err := graphicsOutputDataWithDaemonLimit(d, state, o.attachment, graphicsReset, graphicsBudget)
174 if err != nil {230 if err != nil {
@@ -374,6 +430,7 @@ func (p *preparedOutput) send(data []byte, echoAck uint64, send func(protocol.Ou
374 p.boundary = protocol.UIReceipt{Epoch: p.epoch, State: p.next, ViewPublication: context.Publication, Outcome: protocol.UIReceiptProcessed}430 p.boundary = protocol.UIReceipt{Epoch: p.epoch, State: p.next, ViewPublication: context.Publication, Outcome: protocol.UIReceiptProcessed}
375 }431 }
376 p.stream.next = p.next432 p.stream.next = p.next
433+ p.stream.chargeBytesLocked(p.next, len(data))
377 p.stream.publishOutstanding()434 p.stream.publishOutstanding()
378 p.sent = true435 p.sent = true
379 return nil436 return nil
@@ -510,6 +567,17 @@ func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (protoc
510 return marshalOutputState(data, epoch, 0, 0, echoAck, viewRevision, size, false, nil)567 return marshalOutputState(data, epoch, 0, 0, echoAck, viewRevision, size, false, nil)
511 }568 }
512 569
570+// sideEffectSentLocked charges a side effect that reached the transport. It has
571+// no state of its own, so the ACK of the next state frame releases it: every
572+// state frame is sent after it on the same ordered stream. Caller holds the view
573+// lock that built output, so the epoch cannot have moved.
574+func (s *attachmentOutput) sideEffectSentLocked(output protocol.Output) {
575+ if s == nil || output.Epoch != s.currentEpochLocked() {
576+ return
577+ }
578+ s.chargeBytesLocked(s.next+1, len(output.Data))
579+}
580+
513 func (s *attachmentOutput) ack(epoch, state uint64) bool {581 func (s *attachmentOutput) ack(epoch, state uint64) bool {
514 if s == nil {582 if s == nil {
515 return false583 return false
@@ -520,6 +588,7 @@ func (s *attachmentOutput) ack(epoch, state uint64) bool {
520 return false588 return false
521 }589 }
522 s.acked = state590 s.acked = state
591+ s.releaseBytesLocked(state)
523 s.publishOutstanding()592 s.publishOutstanding()
524 return true593 return true
525 }594 }
@@ -556,6 +625,7 @@ func (s *attachmentOutput) rebaseLocked() {
556 s.epoch = epoch + 1625 s.epoch = epoch + 1
557 s.next = 0626 s.next = 0
558 s.acked = 0627 s.acked = 0
628+ s.resetBytesLocked()
559 s.generation++629 s.generation++
560 s.publishOutstanding()630 s.publishOutstanding()
561 s.maxOutstandingAtomic.Store(s.maxOutstanding)631 s.maxOutstandingAtomic.Store(s.maxOutstanding)
@@ -591,6 +661,11 @@ func (s *attachmentOutput) setWindow(window uint8) {
591 s.maxOutstandingAtomic.Store(s.maxOutstanding)661 s.maxOutstandingAtomic.Store(s.maxOutstanding)
592 }662 }
593 663
664+// atCapacity reports whether the next state frame must wait for an ACK: the
665+// frame window is full, or unacknowledged Output data reached
666+// protocol.MaxOutputWindowBytes while a state frame is in flight. With no state
667+// frame in flight the next frame is always allowed, since only its ACK can
668+// release bytes charged by side effects.
594 func (s *attachmentOutput) atCapacity() bool {669 func (s *attachmentOutput) atCapacity() bool {
595 if s == nil {670 if s == nil {
596 return false671 return false
@@ -599,7 +674,9 @@ func (s *attachmentOutput) atCapacity() bool {
599 if maxOutstanding == 0 {674 if maxOutstanding == 0 {
600 maxOutstanding = s.maxOutstanding675 maxOutstanding = s.maxOutstanding
601 }676 }
602- return s.outstandingAtomic.Load() >= maxOutstanding677+ outstanding := s.outstandingAtomic.Load()
678+ return outstanding >= maxOutstanding ||
679+ (outstanding > 0 && s.unackedBytesAtomic.Load() >= protocol.MaxOutputWindowBytes)
603 }680 }
604 681
605 // committedState reports the latest committed output state number. It takes682 // committedState reports the latest committed output state number. It takes
M internal/usecase/daemon/attachment_output_test.go +109 -0
@@ -519,3 +519,112 @@ func TestNormalizeOutputWindow(t *testing.T) {
519519 require.Equal(t, tt.want, normalizeOutputWindow(tt.input))
520520 }
521521 }
522+
523+// TestAttachmentOutputByteWindow covers the unacknowledged byte budget that
524+// runs alongside the frame window: it fills on large frames and side effects,
525+// never blocks the only frame that can release it, and an ACK or a new epoch
526+// releases exactly what it covers.
527+func TestAttachmentOutputByteWindow(t *testing.T) {
528+ const big = protocol.MaxOutputWindowBytes / 2
529+ type step struct {
530+ frame int // state frame of this many data bytes
531+ sideEffect int // side effect of this many data bytes
532+ ack uint64
533+ rebase bool
534+ }
535+ tests := []struct {
536+ name string
537+ steps []step
538+ wantBytes uint64
539+ wantCapacity bool
540+ }{
541+ {name: "small frames stay under the byte window", steps: []step{{frame: 100}, {frame: 100}}, wantBytes: 200},
542+ {name: "large frames fill the byte window", steps: []step{{frame: big}, {frame: big}}, wantBytes: 2 * big, wantCapacity: true},
543+ {name: "one oversized frame in flight blocks the next", steps: []step{{frame: protocol.MaxOutputWindowBytes + 1}}, wantBytes: protocol.MaxOutputWindowBytes + 1, wantCapacity: true},
544+ {name: "side effects alone never block a state frame", steps: []step{{sideEffect: protocol.MaxOutputWindowBytes}}, wantBytes: protocol.MaxOutputWindowBytes},
545+ {name: "side effects count once a frame is in flight", steps: []step{{frame: 100}, {sideEffect: protocol.MaxOutputWindowBytes}}, wantBytes: protocol.MaxOutputWindowBytes + 100, wantCapacity: true},
546+ {name: "ack releases the frames it covers", steps: []step{{frame: big}, {frame: big}, {ack: 1}}, wantBytes: big},
547+ {name: "a side effect is released by the next state's ack", steps: []step{{sideEffect: 50}, {frame: 100}, {ack: 1}}, wantBytes: 0},
548+ {name: "a new epoch forgets every charge", steps: []step{{frame: big}, {frame: big}, {rebase: true}}, wantBytes: 0},
549+ }
550+ for _, tt := range tests {
551+ t.Run(tt.name, func(t *testing.T) {
552+ stream := newOutputStateStream()
553+ stream.ack(1, 0)
554+ for _, s := range tt.steps {
555+ switch {
556+ case s.frame > 0:
557+ stream.lockView()
558+ stream.next++
559+ stream.chargeBytesLocked(stream.next, s.frame)
560+ stream.publishOutstanding()
561+ stream.unlockView()
562+ case s.sideEffect > 0:
563+ out, err := stream.sideEffect(make([]byte, s.sideEffect), 0)
564+ require.NoError(t, err)
565+ stream.lockView()
566+ stream.sideEffectSentLocked(out)
567+ stream.unlockView()
568+ case s.ack > 0:
569+ require.True(t, stream.ack(stream.currentEpoch(), s.ack))
570+ case s.rebase:
571+ stream.rebase()
572+ }
573+ }
574+ require.Equal(t, tt.wantBytes, stream.unackedBytesAtomic.Load())
575+ require.Equal(t, tt.wantCapacity, stream.atCapacity())
576+ })
577+ }
578+}
579+
580+// TestAttachmentOutputFullByteWindowKeepsImages proves the byte window gates
581+// when a frame goes out, never what it carries: an upload must fit one frame,
582+// so trimming images to the remaining byte budget would drop them for good.
583+func TestAttachmentOutputFullByteWindowKeepsImages(t *testing.T) {
584+ scene := graphics.NewScene(graphics.Limits{})
585+ asset, err := scene.AddAsset(graphics.AssetBlob{Encoded: []byte("asset"), Width: 1, Height: 1})
586+ require.NoError(t, err)
587+ _, err = scene.PlaceAsset(asset, graphics.PixelRect{Width: 1, Height: 1})
588+ require.NoError(t, err)
589+ state := &capturedRenderState{
590+ route: protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"}},
591+ view: attachmentView{tabID: "tab-1"},
592+ panes: []capturedPaneRenderState{{
593+ stableID: "pane-1", focused: true,
594+ graphics: scene.Snapshot(),
595+ graphicsGeometry: domain.Geometry{Size: domain.Size{Cols: 1, Rows: 1}, PixelWidth: 1, PixelHeight: 1},
596+ placement: layout.Placement{Content: domain.Rect{Width: 1, Height: 1}},
597+ }}}
598+ output := attachmentOutputWithGraphics(newGraphicsOutputState())
599+ ac := &attachedClient{output: output, terminalCapabilities: terminalcap.Capabilities{KittyGraphics: true}}
600+ output.attachment = ac
601+ // Side effects filled the byte window while no state frame is in flight.
602+ output.lockView()
603+ output.chargeBytesLocked(output.next+1, protocol.MaxOutputWindowBytes)
604+ output.unlockView()
605+ require.False(t, output.atCapacity(), "the only frame that can release side-effect bytes is allowed")
606+
607+ prepared, err := output.prepareFrame(nil, state, renderer.NewFrame(1, 1), []renderer.Damage{renderer.FullRedraw()}, true, cursorOut{})
608+ require.NoError(t, err)
609+ require.NotNil(t, prepared.graphics, "a full byte window must not drop the frame's images")
610+ require.NotEmpty(t, prepared.graphics.data)
611+ require.Equal(t, protocol.MaxOutputDataLen, prepared.graphics.maxBytes+len(prepared.data)-len(prepared.graphics.data),
612+ "images keep the whole frame data limit")
613+}
614+
615+func TestAttachmentOutputSentFrameChargesItsBytes(t *testing.T) {
616+ stream := newOutputStateStream()
617+ frame := renderer.NewFrame(3, 1)
618+ fillOutputStateRows(frame, []string{"abc"})
619+ prepared, err := stream.prepareTestOutput(frame, nil, true)
620+ require.NoError(t, err)
621+ require.NoError(t, prepared.send(prepared.data, 0, outputFrameSender(func(wire.Envelope) error { return nil })))
622+ require.Equal(t, uint64(len(prepared.data)), stream.unackedBytesAtomic.Load())
623+ require.True(t, stream.ack(stream.currentEpoch(), stream.next))
624+ require.Zero(t, stream.unackedBytesAtomic.Load())
625+
626+ failed, err := stream.prepareTestOutput(frame, nil, true)
627+ require.NoError(t, err)
628+ require.Error(t, failed.send(failed.data, 0, outputFrameSender(func(wire.Envelope) error { return errors.New("send failed") })))
629+ require.Zero(t, stream.unackedBytesAtomic.Load(), "a frame that never left must not be charged")
630+}
@@ -519,3 +519,112 @@ func TestNormalizeOutputWindow(t *testing.T) {
519 require.Equal(t, tt.want, normalizeOutputWindow(tt.input))519 require.Equal(t, tt.want, normalizeOutputWindow(tt.input))
520 }520 }
521 }521 }
522+
523+// TestAttachmentOutputByteWindow covers the unacknowledged byte budget that
524+// runs alongside the frame window: it fills on large frames and side effects,
525+// never blocks the only frame that can release it, and an ACK or a new epoch
526+// releases exactly what it covers.
527+func TestAttachmentOutputByteWindow(t *testing.T) {
528+ const big = protocol.MaxOutputWindowBytes / 2
529+ type step struct {
530+ frame int // state frame of this many data bytes
531+ sideEffect int // side effect of this many data bytes
532+ ack uint64
533+ rebase bool
534+ }
535+ tests := []struct {
536+ name string
537+ steps []step
538+ wantBytes uint64
539+ wantCapacity bool
540+ }{
541+ {name: "small frames stay under the byte window", steps: []step{{frame: 100}, {frame: 100}}, wantBytes: 200},
542+ {name: "large frames fill the byte window", steps: []step{{frame: big}, {frame: big}}, wantBytes: 2 * big, wantCapacity: true},
543+ {name: "one oversized frame in flight blocks the next", steps: []step{{frame: protocol.MaxOutputWindowBytes + 1}}, wantBytes: protocol.MaxOutputWindowBytes + 1, wantCapacity: true},
544+ {name: "side effects alone never block a state frame", steps: []step{{sideEffect: protocol.MaxOutputWindowBytes}}, wantBytes: protocol.MaxOutputWindowBytes},
545+ {name: "side effects count once a frame is in flight", steps: []step{{frame: 100}, {sideEffect: protocol.MaxOutputWindowBytes}}, wantBytes: protocol.MaxOutputWindowBytes + 100, wantCapacity: true},
546+ {name: "ack releases the frames it covers", steps: []step{{frame: big}, {frame: big}, {ack: 1}}, wantBytes: big},
547+ {name: "a side effect is released by the next state's ack", steps: []step{{sideEffect: 50}, {frame: 100}, {ack: 1}}, wantBytes: 0},
548+ {name: "a new epoch forgets every charge", steps: []step{{frame: big}, {frame: big}, {rebase: true}}, wantBytes: 0},
549+ }
550+ for _, tt := range tests {
551+ t.Run(tt.name, func(t *testing.T) {
552+ stream := newOutputStateStream()
553+ stream.ack(1, 0)
554+ for _, s := range tt.steps {
555+ switch {
556+ case s.frame > 0:
557+ stream.lockView()
558+ stream.next++
559+ stream.chargeBytesLocked(stream.next, s.frame)
560+ stream.publishOutstanding()
561+ stream.unlockView()
562+ case s.sideEffect > 0:
563+ out, err := stream.sideEffect(make([]byte, s.sideEffect), 0)
564+ require.NoError(t, err)
565+ stream.lockView()
566+ stream.sideEffectSentLocked(out)
567+ stream.unlockView()
568+ case s.ack > 0:
569+ require.True(t, stream.ack(stream.currentEpoch(), s.ack))
570+ case s.rebase:
571+ stream.rebase()
572+ }
573+ }
574+ require.Equal(t, tt.wantBytes, stream.unackedBytesAtomic.Load())
575+ require.Equal(t, tt.wantCapacity, stream.atCapacity())
576+ })
577+ }
578+}
579+
580+// TestAttachmentOutputFullByteWindowKeepsImages proves the byte window gates
581+// when a frame goes out, never what it carries: an upload must fit one frame,
582+// so trimming images to the remaining byte budget would drop them for good.
583+func TestAttachmentOutputFullByteWindowKeepsImages(t *testing.T) {
584+ scene := graphics.NewScene(graphics.Limits{})
585+ asset, err := scene.AddAsset(graphics.AssetBlob{Encoded: []byte("asset"), Width: 1, Height: 1})
586+ require.NoError(t, err)
587+ _, err = scene.PlaceAsset(asset, graphics.PixelRect{Width: 1, Height: 1})
588+ require.NoError(t, err)
589+ state := &capturedRenderState{
590+ route: protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"}},
591+ view: attachmentView{tabID: "tab-1"},
592+ panes: []capturedPaneRenderState{{
593+ stableID: "pane-1", focused: true,
594+ graphics: scene.Snapshot(),
595+ graphicsGeometry: domain.Geometry{Size: domain.Size{Cols: 1, Rows: 1}, PixelWidth: 1, PixelHeight: 1},
596+ placement: layout.Placement{Content: domain.Rect{Width: 1, Height: 1}},
597+ }}}
598+ output := attachmentOutputWithGraphics(newGraphicsOutputState())
599+ ac := &attachedClient{output: output, terminalCapabilities: terminalcap.Capabilities{KittyGraphics: true}}
600+ output.attachment = ac
601+ // Side effects filled the byte window while no state frame is in flight.
602+ output.lockView()
603+ output.chargeBytesLocked(output.next+1, protocol.MaxOutputWindowBytes)
604+ output.unlockView()
605+ require.False(t, output.atCapacity(), "the only frame that can release side-effect bytes is allowed")
606+
607+ prepared, err := output.prepareFrame(nil, state, renderer.NewFrame(1, 1), []renderer.Damage{renderer.FullRedraw()}, true, cursorOut{})
608+ require.NoError(t, err)
609+ require.NotNil(t, prepared.graphics, "a full byte window must not drop the frame's images")
610+ require.NotEmpty(t, prepared.graphics.data)
611+ require.Equal(t, protocol.MaxOutputDataLen, prepared.graphics.maxBytes+len(prepared.data)-len(prepared.graphics.data),
612+ "images keep the whole frame data limit")
613+}
614+
615+func TestAttachmentOutputSentFrameChargesItsBytes(t *testing.T) {
616+ stream := newOutputStateStream()
617+ frame := renderer.NewFrame(3, 1)
618+ fillOutputStateRows(frame, []string{"abc"})
619+ prepared, err := stream.prepareTestOutput(frame, nil, true)
620+ require.NoError(t, err)
621+ require.NoError(t, prepared.send(prepared.data, 0, outputFrameSender(func(wire.Envelope) error { return nil })))
622+ require.Equal(t, uint64(len(prepared.data)), stream.unackedBytesAtomic.Load())
623+ require.True(t, stream.ack(stream.currentEpoch(), stream.next))
624+ require.Zero(t, stream.unackedBytesAtomic.Load())
625+
626+ failed, err := stream.prepareTestOutput(frame, nil, true)
627+ require.NoError(t, err)
628+ require.Error(t, failed.send(failed.data, 0, outputFrameSender(func(wire.Envelope) error { return errors.New("send failed") })))
629+ require.Zero(t, stream.unackedBytesAtomic.Load(), "a frame that never left must not be charged")
630+}
M internal/usecase/daemon/client.go +8 -1
@@ -591,6 +591,9 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po
591591 if err == nil {
592592 err = expected.transport.SendOutputSynchronous(output)
593593 }
594+ if err == nil {
595+ ac.output.sideEffectSentLocked(output)
596+ }
594597 return expected.transport, err
595598 }
596599 return d.boundedSendWith(expected.transport, func() error {
@@ -605,7 +608,11 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po
605608 if err != nil {
606609 return err
607610 }
608- return expected.transport.SendOutput(output)
611+ if err := expected.transport.SendOutput(output); err != nil {
612+ return err
613+ }
614+ ac.output.sideEffectSentLocked(output)
615+ return nil
609616 })
610617 }
611618
@@ -591,6 +591,9 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po
591 if err == nil {591 if err == nil {
592 err = expected.transport.SendOutputSynchronous(output)592 err = expected.transport.SendOutputSynchronous(output)
593 }593 }
594+ if err == nil {
595+ ac.output.sideEffectSentLocked(output)
596+ }
594 return expected.transport, err597 return expected.transport, err
595 }598 }
596 return d.boundedSendWith(expected.transport, func() error {599 return d.boundedSendWith(expected.transport, func() error {
@@ -605,7 +608,11 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po
605 if err != nil {608 if err != nil {
606 return err609 return err
607 }610 }
608- return expected.transport.SendOutput(output)611+ if err := expected.transport.SendOutput(output); err != nil {
612+ return err
613+ }
614+ ac.output.sideEffectSentLocked(output)
615+ return nil
609 })616 })
610 }617 }
611 618
M internal/usecase/daemon/clipboard.go +3 -0
@@ -293,6 +293,9 @@ func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *atta
293293 err = expected.transport.SendOutput(output)
294294 }
295295 }
296+ if err == nil {
297+ ac.output.sideEffectSentLocked(output)
298+ }
296299 if err != nil {
297300 ticket.reportTransportFailure(expected)
298301 }
@@ -293,6 +293,9 @@ func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *atta
293 err = expected.transport.SendOutput(output)293 err = expected.transport.SendOutput(output)
294 }294 }
295 }295 }
296+ if err == nil {
297+ ac.output.sideEffectSentLocked(output)
298+ }
296 if err != nil {299 if err != nil {
297 ticket.reportTransportFailure(expected)300 ticket.reportTransportFailure(expected)
298 }301 }
M internal/usecase/daemon/graphics_output.go +3 -0
@@ -943,6 +943,9 @@ func (d *Daemon) cleanupAttachmentOutput(ac *attachedClient) error {
943943 _ = ac.closeCapturedTransport(expected.transport)
944944 }
945945 }
946+ if sendErr == nil {
947+ ac.output.sideEffectSentLocked(output)
948+ }
946949 ac.output.unlockView()
947950 if sendErr == nil {
948951 prepared.commit()
@@ -943,6 +943,9 @@ func (d *Daemon) cleanupAttachmentOutput(ac *attachedClient) error {
943 _ = ac.closeCapturedTransport(expected.transport)943 _ = ac.closeCapturedTransport(expected.transport)
944 }944 }
945 }945 }
946+ if sendErr == nil {
947+ ac.output.sideEffectSentLocked(output)
948+ }
946 ac.output.unlockView()949 ac.output.unlockView()
947 if sendErr == nil {950 if sendErr == nil {
948 prepared.commit()951 prepared.commit()

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close