Skip to content

Commit 450d6b8

Browse files
committed
api: enforce Connect stream lifecycle policies
Bound stream idle time and lifetime, track active RPC cancellation, bound terminated unary response writes, and cancel long-lived handlers during shutdown so every admission slot is eventually released. Signed-off-by: Siavash Safi <siavash@cloudflare.com>
1 parent 027f11a commit 450d6b8

10 files changed

Lines changed: 489 additions & 51 deletions

File tree

api/api.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ type Options struct {
8585
// ConnectUnaryTimeout is the timeout for Connect unary RPCs. Zero inherits
8686
// Timeout. A negative value disables the Connect unary timeout.
8787
ConnectUnaryTimeout time.Duration
88+
// ConnectStreamIdleTimeout limits the time between messages on a Connect
89+
// stream. Non-positive values disable the timeout.
90+
ConnectStreamIdleTimeout time.Duration
91+
// ConnectStreamLifetime limits the total lifetime of a Connect stream.
92+
// Non-positive values disable the timeout.
93+
ConnectStreamLifetime time.Duration
8894
// ConnectReadMaxBytes limits each incoming Connect protobuf message.
8995
// Non-positive values do not set a limit.
9096
ConnectReadMaxBytes int
@@ -149,6 +155,8 @@ func (o Options) resolve() effectiveOptions {
149155
UnaryConcurrency: unaryConcurrency,
150156
StreamConcurrency: streamConcurrency,
151157
UnaryTimeout: unaryTimeout,
158+
StreamIdleTimeout: max(o.ConnectStreamIdleTimeout, 0),
159+
StreamLifetime: max(o.ConnectStreamLifetime, 0),
152160
ReadMaxBytes: max(o.ConnectReadMaxBytes, 0),
153161
SendMaxBytes: max(o.ConnectSendMaxBytes, 0),
154162
MaxRequestBodyBytes: max(o.ConnectMaxRequestBodyBytes, 0),
@@ -320,6 +328,13 @@ func (api *API) Update(cfg *config.Config, setAlertStatus func(ctx context.Conte
320328
}
321329
}
322330

331+
// Shutdown rejects new Connect RPCs and cancels active RPCs.
332+
func (api *API) Shutdown() {
333+
if api.connect != nil {
334+
api.connect.Shutdown()
335+
}
336+
}
337+
323338
func (api *API) limitHandler(h http.Handler) http.Handler {
324339
limited := api.concurrencyLimitHandler(h)
325340
if api.timeout <= 0 {

api/api_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func TestOptionsResolve(t *testing.T) {
8787
effective := (Options{
8888
Concurrency: 3,
8989
Timeout: time.Minute,
90+
ConnectStreamIdleTimeout: -time.Second,
91+
ConnectStreamLifetime: -time.Second,
9092
ConnectReadMaxBytes: -1,
9193
ConnectSendMaxBytes: -1,
9294
ConnectMaxRequestBodyBytes: -1,
@@ -96,6 +98,8 @@ func TestOptionsResolve(t *testing.T) {
9698
require.Equal(t, 3, effective.connect.UnaryConcurrency)
9799
require.Equal(t, 3, effective.connect.StreamConcurrency)
98100
require.Equal(t, time.Minute, effective.connect.UnaryTimeout)
101+
require.Zero(t, effective.connect.StreamIdleTimeout)
102+
require.Zero(t, effective.connect.StreamLifetime)
99103
require.Zero(t, effective.connect.ReadMaxBytes)
100104
require.Zero(t, effective.connect.SendMaxBytes)
101105
require.Zero(t, effective.connect.MaxRequestBodyBytes)

0 commit comments

Comments
 (0)