Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add close() API #126

Merged
29 changes: 25 additions & 4 deletions templates/typescript_gapic/src/$version/$service_client.ts.njk
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ export interface PaginationResponse<
*/
export class {{ service.name }}Client {
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _{{ service.name.toCamelCase() }}Stub: ClientStub;
private _innerApiCalls: {[name: string]: Function};
{%- if (service.pathTemplates.length > 0) %}
private _pathTemplates: {[name: string]: gax.PathTemplate};
private _terminated: false;
{%- endif %}
auth: gax.GoogleAuth;

Expand Down Expand Up @@ -233,7 +235,7 @@ export class {{ service.name }}Client {

// Put together the "service stub" for
// {{api.naming.protoPackage}}.{{ service.name }}.
const {{ service.name.toCamelCase() }}Stub = gaxGrpc.createStub(
this._{{ service.name.toCamelCase() }}Stub = gaxGrpc.createStub(
opts.fallback ?
(protos as protobuf.Root).lookupService('{{api.naming.protoPackage}}.{{ service.name }}') :
// tslint:disable-next-line no-any
Expand All @@ -252,21 +254,28 @@ export class {{ service.name }}Client {
];

for (const methodName of {{ service.name.toCamelCase() }}StubMethods) {
const innerCallPromise = {{ service.name.toCamelCase() }}Stub.then(
const innerCallPromise = this._{{ service.name.toCamelCase() }}Stub.then(
(stub: {[method: string]: Function}) => (...args: Array<{}>) => {
return stub[methodName].apply(stub, args);
},
(err: Error|null|undefined) => () => {
throw err;
});

this._innerApiCalls[methodName] = gaxModule.createApiCall(
const apiCall = gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
);

this._innerApiCalls[methodName] = (arguments, callOptions, callback) => {
if (!this._terminated) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the condition must be reversed

return Promise.reject('The client has already been closed.');
}
return apiCall(arguments, callOptions, callback);
};
}
}

Expand Down Expand Up @@ -587,4 +596,16 @@ export class {{ service.name }}Client {
{%- endfor %}
{%- endfor %}
{%- endif %}

/**
* Terminate the GRPC channel and close the client.
*
* The client will no longer be usable and all future behavior is undefined.
*/
close(): void {
if (!this._terminated) {
this._terminated = true;
this._{{ service.name.toCamelCase() }}Stub.then(stub => stub.close());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface PaginationResponse<
*/
export class KeyManagementServiceClient {
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _keyManagementServiceStub: ClientStub;
private _innerApiCalls: {[name: string]: Function};
auth: gax.GoogleAuth;

Expand Down Expand Up @@ -173,7 +174,7 @@ export class KeyManagementServiceClient {

// Put together the "service stub" for
// google.cloud.kms.v1.KeyManagementService.
const keyManagementServiceStub = gaxGrpc.createStub(
this._keyManagementServiceStub = gaxGrpc.createStub(
opts.fallback ?
(protos as protobuf.Root).lookupService('google.cloud.kms.v1.KeyManagementService') :
// tslint:disable-next-line no-any
Expand All @@ -186,21 +187,28 @@ export class KeyManagementServiceClient {
['listKeyRings', 'listCryptoKeys', 'listCryptoKeyVersions', 'listImportJobs', 'getKeyRing', 'getCryptoKey', 'getCryptoKeyVersion', 'getPublicKey', 'getImportJob', 'createKeyRing', 'createCryptoKey', 'createCryptoKeyVersion', 'importCryptoKeyVersion', 'createImportJob', 'updateCryptoKey', 'updateCryptoKeyVersion', 'encrypt', 'decrypt', 'asymmetricSign', 'asymmetricDecrypt', 'updateCryptoKeyPrimaryVersion', 'destroyCryptoKeyVersion', 'restoreCryptoKeyVersion'];

for (const methodName of keyManagementServiceStubMethods) {
const innerCallPromise = keyManagementServiceStub.then(
const innerCallPromise = this._keyManagementServiceStub.then(
(stub: {[method: string]: Function}) => (...args: Array<{}>) => {
return stub[methodName].apply(stub, args);
},
(err: Error|null|undefined) => () => {
throw err;
});

this._innerApiCalls[methodName] = gaxModule.createApiCall(
const apiCall = gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
);

this._innerApiCalls[methodName] = (arguments, callOptions, callback) => {
if (!this._terminated) {
return Promise.reject('The client has already been closed.');
}
return apiCall(arguments, callOptions, callback);
};
}
}

Expand Down Expand Up @@ -2066,4 +2074,16 @@ export class KeyManagementServiceClient {
callSettings
);
}

/**
* Terminate the GRPC channel and close the client.
*
* The client will no longer be usable and all future behavior is undefined.
*/
close(): void {
if (!this._terminated) {
this._terminated = true;
this._keyManagementServiceStub.then(stub => stub.close());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface PaginationResponse<
*/
export class EchoClient {
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _echoStub: ClientStub;
private _innerApiCalls: {[name: string]: Function};
auth: gax.GoogleAuth;

Expand Down Expand Up @@ -191,7 +192,7 @@ export class EchoClient {

// Put together the "service stub" for
// google.showcase.v1beta1.Echo.
const echoStub = gaxGrpc.createStub(
this._echoStub = gaxGrpc.createStub(
opts.fallback ?
(protos as protobuf.Root).lookupService('google.showcase.v1beta1.Echo') :
// tslint:disable-next-line no-any
Expand All @@ -204,21 +205,28 @@ export class EchoClient {
['echo', 'expand', 'collect', 'chat', 'pagedExpand', 'wait'];

for (const methodName of echoStubMethods) {
const innerCallPromise = echoStub.then(
const innerCallPromise = this._echoStub.then(
(stub: {[method: string]: Function}) => (...args: Array<{}>) => {
return stub[methodName].apply(stub, args);
},
(err: Error|null|undefined) => () => {
throw err;
});

this._innerApiCalls[methodName] = gaxModule.createApiCall(
const apiCall = gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
);

this._innerApiCalls[methodName] = (arguments, callOptions, callback) => {
if (!this._terminated) {
return Promise.reject('The client has already been closed.');
}
return apiCall(arguments, callOptions, callback);
};
}
}

Expand Down Expand Up @@ -578,4 +586,16 @@ export class EchoClient {
callSettings
);
}

/**
* Terminate the GRPC channel and close the client.
*
* The client will no longer be usable and all future behavior is undefined.
*/
close(): void {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we should return a Promise here, so that users can await it?

Suggested change
close(): void {
close(): Promise<void> {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but this._{{ service.name.toCamelCase() }}Stub is a Promise and we use it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I was hoping close() could be synchronous, but you are right. I updated the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, due to the asynchronous nature of the constructor (it fires some promises to initialize the stubs), all member functions working with a stub must be asynchronous (must wait on the stubs to be complete).

if (!this._terminated) {
this._terminated = true;
this._echoStub.then(stub => stub.close());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const version = require('../../../package.json').version;
*/
export class TextToSpeechClient {
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _textToSpeechStub: ClientStub;
private _innerApiCalls: {[name: string]: Function};
auth: gax.GoogleAuth;

Expand Down Expand Up @@ -135,7 +136,7 @@ export class TextToSpeechClient {

// Put together the "service stub" for
// google.cloud.texttospeech.v1.TextToSpeech.
const textToSpeechStub = gaxGrpc.createStub(
this._textToSpeechStub = gaxGrpc.createStub(
opts.fallback ?
(protos as protobuf.Root).lookupService('google.cloud.texttospeech.v1.TextToSpeech') :
// tslint:disable-next-line no-any
Expand All @@ -148,21 +149,28 @@ export class TextToSpeechClient {
['listVoices', 'synthesizeSpeech'];

for (const methodName of textToSpeechStubMethods) {
const innerCallPromise = textToSpeechStub.then(
const innerCallPromise = this._textToSpeechStub.then(
(stub: {[method: string]: Function}) => (...args: Array<{}>) => {
return stub[methodName].apply(stub, args);
},
(err: Error|null|undefined) => () => {
throw err;
});

this._innerApiCalls[methodName] = gaxModule.createApiCall(
const apiCall = gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
);

this._innerApiCalls[methodName] = (arguments, callOptions, callback) => {
if (!this._terminated) {
return Promise.reject('The client has already been closed.');
}
return apiCall(arguments, callOptions, callback);
};
}
}

Expand Down Expand Up @@ -334,4 +342,16 @@ export class TextToSpeechClient {
return this._innerApiCalls.synthesizeSpeech(request, options, callback);
}


/**
* Terminate the GRPC channel and close the client.
*
* The client will no longer be usable and all future behavior is undefined.
*/
close(): void {
if (!this._terminated) {
this._terminated = true;
this._textToSpeechStub.then(stub => stub.close());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ export interface PaginationResponse<
*/
export class TranslationServiceClient {
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _translationServiceStub: ClientStub;
private _innerApiCalls: {[name: string]: Function};
private _pathTemplates: {[name: string]: gax.PathTemplate};
private _terminated: false;
auth: gax.GoogleAuth;

/**
Expand Down Expand Up @@ -209,7 +211,7 @@ export class TranslationServiceClient {

// Put together the "service stub" for
// google.cloud.translation.v3beta1.TranslationService.
const translationServiceStub = gaxGrpc.createStub(
this._translationServiceStub = gaxGrpc.createStub(
opts.fallback ?
(protos as protobuf.Root).lookupService('google.cloud.translation.v3beta1.TranslationService') :
// tslint:disable-next-line no-any
Expand All @@ -222,21 +224,28 @@ export class TranslationServiceClient {
['translateText', 'detectLanguage', 'getSupportedLanguages', 'batchTranslateText', 'createGlossary', 'listGlossaries', 'getGlossary', 'deleteGlossary'];

for (const methodName of translationServiceStubMethods) {
const innerCallPromise = translationServiceStub.then(
const innerCallPromise = this._translationServiceStub.then(
(stub: {[method: string]: Function}) => (...args: Array<{}>) => {
return stub[methodName].apply(stub, args);
},
(err: Error|null|undefined) => () => {
throw err;
});

this._innerApiCalls[methodName] = gaxModule.createApiCall(
const apiCall = gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
);

this._innerApiCalls[methodName] = (arguments, callOptions, callback) => {
if (!this._terminated) {
return Promise.reject('The client has already been closed.');
}
return apiCall(arguments, callOptions, callback);
};
}
}

Expand Down Expand Up @@ -1053,4 +1062,16 @@ export class TranslationServiceClient {
matchGlossaryFromGlossaryName(glossaryName: string){
return this._pathTemplates.glossaryPathTemplate.match(glossaryName).glossary;
}

/**
* Terminate the GRPC channel and close the client.
*
* The client will no longer be usable and all future behavior is undefined.
*/
close(): void {
if (!this._terminated) {
this._terminated = true;
this._translationServiceStub.then(stub => stub.close());
}
}
}
21 changes: 21 additions & 0 deletions typescript/test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ function checkIdenticalFile(
const readOutput = fs.readFileSync(outputFullPath).toString();
const baselineOutput = fs.readFileSync(baselineFullPath).toString();
if (readOutput === baselineOutput) return IDENTICAL_FILE;

const readOutputLines = readOutput.split('\n');
const baselineOutputLines = baselineOutput.split('\n');

if (readOutputLines.length !== baselineOutputLines.length) {
console.warn(
`Line count for ${outputFullPath} was ${readOutputLines.length}, ` +
`but expected ${baselineOutputLines.length}.`
);
} else {
for (let i = 0; i < readOutputLines.length; ++i) {
if (readOutputLines[i] !== baselineOutputLines[i]) {
console.warn(
`Line ${i + 1} of ${outputFullPath} was \n\t"${
readOutputLines[i]
}"\nbut expected\n\t"${baselineOutputLines[i]}"`
);
}
}
}

return FILE_WITH_DIFF_CONTENT;
}

Expand Down