diff --git a/handwritten/spanner/src/index.ts b/handwritten/spanner/src/index.ts index 17287fba98b..a882114c91c 100644 --- a/handwritten/spanner/src/index.ts +++ b/handwritten/spanner/src/index.ts @@ -155,6 +155,8 @@ export type GetInstanceConfigOperationsCallback = PagedCallback< * DirectedReadOptions won't be set for readWrite transactions" * @property {ObservabilityOptions} [observabilityOptions] Sets the observability options to be used for OpenTelemetry tracing * @property {boolean} [disableBuiltInMetrics=True] If set to true, built-in metrics will be disabled. + * @property {number} ['grpc.enable_channelz'=0] Whether to enable gRPC Channelz service tracking. + * Defaults to 0 (disabled) to eliminate per-RPC tracking and allocation overhead. Set to 1 to enable. */ export interface SpannerOptions extends GrpcClientOptions { apiEndpoint?: string; @@ -182,6 +184,12 @@ export interface SpannerOptions extends GrpcClientOptions { */ universe_domain?: string; universeDomain?: string; + /** + * Whether to enable gRPC Channelz service tracking. + * Defaults to `0` (disabled) to eliminate per-RPC allocation and tracking overhead. + * Set to `1` if live connection introspection via gRPC Channelz (e.g. grpcdebug) is required. + */ + 'grpc.enable_channelz'?: number; } export interface RequestConfig { client: string; @@ -423,6 +431,8 @@ class Spanner extends GrpcService { scopes, // Add grpc keep alive setting 'grpc.keepalive_time_ms': 120000, + // Disable Channelz by default to reduce per-RPC tracking and allocation overhead + 'grpc.enable_channelz': 0, // Enable grpc-gcp support 'grpc.callInvocationTransformer': grpcGcp.gcpCallInvocationTransformer, 'grpc.channelFactoryOverride': grpcGcp.gcpChannelFactoryOverride, @@ -534,7 +544,7 @@ class Spanner extends GrpcService { if (!this.clients_.has(clientName)) { this.clients_.set( clientName, - new v1[clientName](this.options as ClientOptions), + new v1.InstanceAdminClient(this.options as ClientOptions), ); } return this.clients_.get(clientName)! as v1.InstanceAdminClient; @@ -558,7 +568,7 @@ class Spanner extends GrpcService { if (!this.clients_.has(clientName)) { this.clients_.set( clientName, - new v1[clientName](this.options as ClientOptions), + new v1.DatabaseAdminClient(this.options as ClientOptions), ); } return this.clients_.get(clientName)! as v1.DatabaseAdminClient; @@ -615,10 +625,9 @@ class Spanner extends GrpcService { if (callback) { // process.nextTick prevents Unhandled Promise Rejections if callback throws - res.then( - () => process.nextTick(() => callback(null)), - err => process.nextTick(() => callback(err)), - ); + res + .then(() => process.nextTick(() => callback(null))) + .catch(err => process.nextTick(() => callback(err))); } else { return res; } @@ -1727,7 +1736,10 @@ class Spanner extends GrpcService { const clientName = config.client; try { if (!this.clients_.has(clientName)) { - this.clients_.set(clientName, new v1[clientName](this.options)); + this.clients_.set( + clientName, + new (v1 as Record)[clientName](this.options), + ); } } catch (err) { callback(err, null); @@ -1896,6 +1908,7 @@ class Spanner extends GrpcService { .then(val => { metricsTracer?.recordOperationCompletion(); resolve(val); + return val; }) .catch(error => { metricsTracer?.recordOperationCompletion(); diff --git a/handwritten/spanner/test/index.ts b/handwritten/spanner/test/index.ts index 6652d25fec6..c2d18e73767 100644 --- a/handwritten/spanner/test/index.ts +++ b/handwritten/spanner/test/index.ts @@ -230,6 +230,7 @@ describe('Spanner', () => { scopes: [], grpc, 'grpc.keepalive_time_ms': 120000, + 'grpc.enable_channelz': 0, 'grpc.callInvocationTransformer': fakeGrpcGcp().gcpCallInvocationTransformer, 'grpc.channelFactoryOverride': fakeGrpcGcp().gcpChannelFactoryOverride, @@ -295,6 +296,22 @@ describe('Spanner', () => { ); }); + it('should disable channelz by default and allow overriding it', () => { + const spannerDefault = new Spanner(OPTIONS); + assert.strictEqual( + (spannerDefault.options as any)['grpc.enable_channelz'], + 0, + ); + + const spannerEnabled = new Spanner( + Object.assign({}, OPTIONS, {'grpc.enable_channelz': 1}), + ); + assert.strictEqual( + (spannerEnabled.options as any)['grpc.enable_channelz'], + 1, + ); + }); + it('should inherit from GrpcService', () => { assert(spanner instanceof FakeGrpcService); @@ -2314,6 +2331,7 @@ describe('Spanner', () => { return spanner.request(CONFIG).then(result => { assert.strictEqual(result, gapicRequestFnResult); + return result; }); }); }); diff --git a/handwritten/spanner/test/spanner.ts b/handwritten/spanner/test/spanner.ts index 926aeb8ca63..5c25c31bc4f 100644 --- a/handwritten/spanner/test/spanner.ts +++ b/handwritten/spanner/test/spanner.ts @@ -30,6 +30,7 @@ import { Snapshot, Spanner, Transaction, + v1 as gapicV1, } from '../src'; import * as mock from './mockserver/mockspanner'; import { @@ -386,6 +387,49 @@ describe('Spanner with mock server', () => { assert.notStrictEqual(dbWithDefaultOptions, dbWithWriteSessions); }); + it('should disable channelz by default and allow overriding it', async () => { + assert.strictEqual((spanner.options as any)['grpc.enable_channelz'], 0); + + const client = new gapicV1.SpannerClient(spanner.options as any); + await client.initialize(); + const stub = (await (client as any).spannerStub) as any; + const channel = stub.getChannel(); + const pooledChannel = channel.channelRefs?.[0]?.channel as any; + assert.ok(pooledChannel, 'Expected pooledChannel to be initialized'); + assert.strictEqual(pooledChannel.internalChannel?.channelzEnabled, false); + + const customSpanner = new Spanner({ + servicePath: 'localhost', + port, + sslCreds: grpc.credentials.createInsecure(), + 'grpc.enable_channelz': 1, + }); + try { + assert.strictEqual( + (customSpanner.options as any)['grpc.enable_channelz'], + 1, + ); + const customClient = new gapicV1.SpannerClient( + customSpanner.options as any, + ); + await customClient.initialize(); + const customStub = (await (customClient as any).spannerStub) as any; + const customChannel = customStub.getChannel(); + const customPooledChannel = customChannel.channelRefs?.[0] + ?.channel as any; + assert.ok( + customPooledChannel, + 'Expected customPooledChannel to be initialized', + ); + assert.strictEqual( + customPooledChannel.internalChannel?.channelzEnabled, + true, + ); + } finally { + await customSpanner.close(); + } + }); + it('should execute query', async () => { // The query to execute const query = {