Skip to content

Expose gRPC channel options (keepalive) so an unusable channel fails fast instead of hanging #547

Description

@RoyaleGolf

Problem

When the underlying gRPC channel becomes unusable (half-open / silently dropped), RPCs appear to hang until the caller's own timeout rather than failing fast. There is currently no way to configure grpc.keepalive_time_ms / keepalive_timeout_ms / keepalive_permit_without_calls, because ClientOptions does not accept channel options and loadService() constructs the service with only sslCreds:

const client = new protoService({ sslCreds: this.getCredentials() });

ClientOptions is identical on 23.0.0 and 24.1.0:

{ client_id, client_secret, developer_token, disable_parsing?, max_reporting_rows? }

Why the existing service cache doesn't appear to mitigate this

serviceCache has a 10-minute TTL with a dispose that closes the service, which looks like it should self-heal. In our environment it did not. @grpc/grpc-js maintains a global subchannel pool keyed by target and channel args, so a rebuilt service can draw the same underlying connection. We saw calls 29 minutes apart both hang — i.e. after the cached service should already have been evicted and recreated. Only a process restart restored normal behaviour.

To be clear about the limits of this evidence: we did not capture a socket-level trace, so the subchannel-pool explanation is inferred from observed behaviour rather than directly proven. What we can state confidently is that the condition was process-scoped and survived the service cache TTL.

Impact

Offline conversion uploads hang for the full caller timeout and return no error, so callers cannot distinguish "connection is unusable" from "the API is slow", and cannot recover in-process. Because there is no fast failure signal, this went undetected across multiple days over roughly a month in our case.

Evidence that the fault is transport-level rather than credentials or API-side

Captured during an active occurrence, same account and same credentials:

Path Result
gRPC uploadClickConversions (this SDK) hung >60s, repeatedly
OAuth token refresh (REST) HTTP 200 in 0.13s
googleAds:search v25 (REST) HTTP 200 in 0.34s
Same rows retried after a process restart uploaded immediately

Other outbound integrations in the same process and same time window (all REST/HTTP1) were unaffected.

Requested change

Allow channel options to be passed through to the service constructor — for example an optional channel_options on ClientOptions, forwarded into:

new protoService({ sslCreds: this.getCredentials(), ...channelOptions });

Including those options in the serviceCache key would keep differing configurations isolated from one another.

Deliberately scoped to that passthrough and the cache-key change — no change to defaults or existing behaviour for callers who don't opt in. Happy to open a PR if you'd like it shaped a particular way.

Environment

  • google-ads-api 23.0.0 (surface also verified against 24.1.0)
  • @grpc/grpc-js 1.14.4
  • google-gax 5.0.6
  • Node, running on Railway

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions