You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
crates/openshell-gateway-interceptors/src/plan.rs:860-872connect_endpoint builds a bare Endpoint::from_shared(endpoint).connect(). It sets no connect_timeout, no HTTP/2 keepalive, no TCP keepalive, no idle timeout.
The unix-socket branch connect_unix_endpoint at crates/openshell-gateway-interceptors/src/plan.rs:874-884 is likewise bare.
These channels are long-lived: ExecutionPlan::load (plan.rs:186) dials once at startup and the resulting Channel is cloned into every BindingPlan and GatewayInterceptorProfileSource, reused for the process lifetime.
Every other long-lived gRPC client in the repo already configures keepalive. Canonical helper standard_endpoint at crates/openshell-sdk/src/transport.rs:31-39; most complete variant crates/openshell-core/src/grpc_client.rs:139-145 (http2_keep_alive_interval(10s), keep_alive_while_idle(true), keep_alive_timeout(20s), http2_adaptive_window(true)). The interceptor channel is an outlier.
tonic resolves to 0.14.6 (Cargo.lock); all keepalive setters plus connect_timeout are available on Endpoint.
Remaining reason for filing: interceptors sit at the gateway gRPC routing boundary and run on the hot path of intercepted RPCs. A stale idle channel or an unreachable interceptor host degrades every affected RPC, and with no connect_timeout the initial dial can hang far longer than intended.
Description
Actual behavior:connect_endpoint in crates/openshell-gateway-interceptors/src/plan.rs establishes the interceptor gRPC channel with no connect timeout and no keepalive. An idle channel is not kept alive, so an intermediary idle-reap, a load-balancer timeout, an interceptor redeploy, or a GOAWAY silently invalidates the connection; the failure only surfaces on the next interceptor evaluation. With no connect_timeout, an unreachable interceptor host at startup or reconnect can hang on the OS default TCP connect timeout.
Expected behavior: the interceptor channel follows the same connect-timeout and HTTP/2 keepalive configuration as every other long-lived gRPC client in the repo, so idle connections survive intermediary idle timeouts, dead peers are detected proactively, and dials are bounded.
This is the follow-up deferred by #2474, which fixed the supervisor-middleware channel and noted: "Consider whether crates/openshell-gateway-interceptors/src/plan.rs should be fixed in the same change. It builds a bare Endpoint::from_shared(...).connect() with no keepalive and no connect timeout at all, so it has the same gap plus a missing connect bound."
Reproduction Steps
Configure a gateway interceptor ([[...interceptor]]) pointing at an external gRPC interceptor service.
Place any idle-reaping hop between the gateway and the interceptor service, or restart the interceptor service.
Leave the interceptor channel idle past that idle timeout without an intercepted RPC.
Issue an RPC that matches an interceptor binding. The evaluation runs on a stale connection and fails, subject to the binding's FailurePolicy.
Observable without an intermediary by inspecting the constructed Endpoint: no keepalive is set, so no HTTP/2 PING frames are ever emitted on an idle interceptor channel, and no connect_timeout bounds the dial.
Environment
OS: macOS (darwin 25.5.0)
OpenShell: main at 83284129
tonic: 0.14.6
Latest release checked: yes — tested against current main, ahead of latest release
Apply the repo-standard channel configuration in connect_endpoint (and the unix branch), matching crates/openshell-core/src/grpc_client.rs:139-145:
connect_timeout (bounded dial; pick a value consistent with the repo's other clients / the middleware channel's 5s)
http2_keep_alive_interval(10s)
keep_alive_while_idle(true)
keep_alive_timeout(20s)
http2_adaptive_window(true)
Prefer routing this through the existing canonical helper (standard_endpoint in openshell-sdk or the openshell-core variant) rather than open-coding the setters, so the interceptor channel stays consistent with the rest of the repo. These are platform constants, not operator-tunable config.
Testing
Assert the constructed endpoint carries the expected connect-timeout and keepalive settings, following whatever pattern openshell-core / openshell-sdk already use.
Verify an interceptor evaluation succeeds against a service that has been idle past a configured intermediary idle timeout.
Agent Diagnostic
create-github-issuemainat83284129main, which is ahead of the latest releasekeepalive/connect_timeout/gateway-interceptors; no dedicated issue exists for the interceptor channel. fix(supervisor-middleware): configure HTTP/2 keepalive on the middleware gRPC channel #2474 covers the supervisor-middleware channel and only mentions this file as a follow-upcrates/openshell-gateway-interceptors/src/plan.rs:860-872connect_endpointbuilds a bareEndpoint::from_shared(endpoint).connect(). It sets noconnect_timeout, no HTTP/2 keepalive, no TCP keepalive, no idle timeout.connect_unix_endpointatcrates/openshell-gateway-interceptors/src/plan.rs:874-884is likewise bare.ExecutionPlan::load(plan.rs:186) dials once at startup and the resultingChannelis cloned into everyBindingPlanandGatewayInterceptorProfileSource, reused for the process lifetime.standard_endpointatcrates/openshell-sdk/src/transport.rs:31-39; most complete variantcrates/openshell-core/src/grpc_client.rs:139-145(http2_keep_alive_interval(10s),keep_alive_while_idle(true),keep_alive_timeout(20s),http2_adaptive_window(true)). The interceptor channel is an outlier.Cargo.lock); all keepalive setters plusconnect_timeoutare available onEndpoint.connect_timeoutthe initial dial can hang far longer than intended.Description
Actual behavior:
connect_endpointincrates/openshell-gateway-interceptors/src/plan.rsestablishes the interceptor gRPC channel with no connect timeout and no keepalive. An idle channel is not kept alive, so an intermediary idle-reap, a load-balancer timeout, an interceptor redeploy, or a GOAWAY silently invalidates the connection; the failure only surfaces on the next interceptor evaluation. With noconnect_timeout, an unreachable interceptor host at startup or reconnect can hang on the OS default TCP connect timeout.Expected behavior: the interceptor channel follows the same connect-timeout and HTTP/2 keepalive configuration as every other long-lived gRPC client in the repo, so idle connections survive intermediary idle timeouts, dead peers are detected proactively, and dials are bounded.
This is the follow-up deferred by #2474, which fixed the supervisor-middleware channel and noted: "Consider whether
crates/openshell-gateway-interceptors/src/plan.rsshould be fixed in the same change. It builds a bareEndpoint::from_shared(...).connect()with no keepalive and no connect timeout at all, so it has the same gap plus a missing connect bound."Reproduction Steps
[[...interceptor]]) pointing at an external gRPC interceptor service.FailurePolicy.Observable without an intermediary by inspecting the constructed
Endpoint: no keepalive is set, so no HTTP/2 PING frames are ever emitted on an idle interceptor channel, and noconnect_timeoutbounds the dial.Environment
mainat83284129main, ahead of latest releaseProposed Fix
Apply the repo-standard channel configuration in
connect_endpoint(and the unix branch), matchingcrates/openshell-core/src/grpc_client.rs:139-145:connect_timeout(bounded dial; pick a value consistent with the repo's other clients / the middleware channel's 5s)http2_keep_alive_interval(10s)keep_alive_while_idle(true)keep_alive_timeout(20s)http2_adaptive_window(true)Prefer routing this through the existing canonical helper (
standard_endpointinopenshell-sdkor theopenshell-corevariant) rather than open-coding the setters, so the interceptor channel stays consistent with the rest of the repo. These are platform constants, not operator-tunable config.Testing
openshell-core/openshell-sdkalready use.