diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 6242505e..b1c7ba2e 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -70,6 +70,7 @@ export interface SecureConnectResult { export interface SecureConnector { connect(socket: Socket): Promise; + waitForReady(): Promise; getCallCredentials(): CallCredentials; destroy(): void; } @@ -188,6 +189,9 @@ class InsecureChannelCredentialsImpl extends ChannelCredentials { secure: false }); }, + waitForReady: () => { + return Promise.resolve(); + }, getCallCredentials: () => { return callCredentials ?? CallCredentials.createEmpty(); }, @@ -276,6 +280,9 @@ class SecureConnectorImpl implements SecureConnector { }); }); } + waitForReady(): Promise { + return Promise.resolve(); + } getCallCredentials(): CallCredentials { return this.callCredentials; } @@ -333,17 +340,28 @@ class CertificateProviderChannelCredentialsImpl extends ChannelCredentials { connect(socket: Socket): Promise { return new Promise(async (resolve, reject) => { - const secureContext = await this.parent.getSecureContext(); + const secureContext = this.parent.getLatestSecureContext(); if (!secureContext) { reject(new Error('Failed to load credentials')); return; } + if (socket.closed) { + reject(new Error('Socket closed while loading credentials')); + } const connnectionOptions = getConnectionOptions(secureContext, this.parent.verifyOptions, this.channelTarget, this.options); const tlsConnectOptions: ConnectionOptions = { socket: socket, ...connnectionOptions } + const closeCallback = () => { + reject(new Error('Socket closed')); + }; + const errorCallback = (error: Error) => { + reject(error); + } const tlsSocket = tlsConnect(tlsConnectOptions, () => { + tlsSocket.removeListener('close', closeCallback); + tlsSocket.removeListener('error', errorCallback); if (!tlsSocket.authorized) { reject(tlsSocket.authorizationError); return; @@ -353,12 +371,15 @@ class CertificateProviderChannelCredentialsImpl extends ChannelCredentials { secure: true }); }); - tlsSocket.on('error', (error: Error) => { - reject(error); - }); + tlsSocket.once('close', closeCallback); + tlsSocket.once('error', errorCallback); }); } + async waitForReady(): Promise { + await this.parent.getSecureContext(); + } + getCallCredentials(): CallCredentials { return this.callCredentials; } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 7f657011..f6f36d4d 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -717,12 +717,19 @@ export class Http2SubchannelConnector implements SubchannelConnector { return proxiedSocket; } else { return new Promise((resolve, reject) => { + const closeCallback = () => { + reject(new Error('Socket closed')); + }; + const errorCallback = (error: Error) => { + reject(error); + } const socket = net.connect(address, () => { + socket.removeListener('close', closeCallback); + socket.removeListener('error', errorCallback); resolve(socket); }); - socket.once('error', (error) => { - reject(error); - }); + socket.once('close', closeCallback); + socket.once('error', errorCallback); }); } }); @@ -740,6 +747,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { let secureConnectResult: SecureConnectResult | null = null; const addressString = subchannelAddressToString(address); try { + await secureConnector.waitForReady(); tcpConnection = await this.tcpConnect(address, options); this.trace(addressString + ' ' + 'Established TCP connection'); secureConnectResult = await secureConnector.connect(tcpConnection);