mirror of https://github.com/grpc/grpc-node.git
grpc-js: Fix handling of calls after resolution failure
This commit is contained in:
parent
7664a49a99
commit
6c686772cb
|
@ -20,6 +20,7 @@ import {
|
|||
Call,
|
||||
Http2CallStream,
|
||||
CallStreamOptions,
|
||||
StatusObject,
|
||||
} from './call-stream';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
|
@ -170,6 +171,14 @@ export class ChannelImplementation implements Channel {
|
|||
*/
|
||||
private callRefTimer: NodeJS.Timer;
|
||||
private configSelector: ConfigSelector | null = null;
|
||||
/**
|
||||
* This is the error from the name resolver if it failed most recently. It
|
||||
* is only used to end calls that start while there is no config selector
|
||||
* and the name resolver is in backoff, so it should be nulled if
|
||||
* configSelector becomes set or the channel state becomes anything other
|
||||
* than TRANSIENT_FAILURE.
|
||||
*/
|
||||
private currentResolutionError: StatusObject | null = null;
|
||||
|
||||
// Channelz info
|
||||
private readonly channelzEnabled: boolean = true;
|
||||
|
@ -290,6 +299,7 @@ export class ChannelImplementation implements Channel {
|
|||
this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded');
|
||||
}
|
||||
this.configSelector = configSelector;
|
||||
this.currentResolutionError = null;
|
||||
/* We process the queue asynchronously to ensure that the corresponding
|
||||
* load balancer update has completed. */
|
||||
process.nextTick(() => {
|
||||
|
@ -309,6 +319,9 @@ export class ChannelImplementation implements Channel {
|
|||
if (this.configSelectionQueue.length > 0) {
|
||||
this.trace('Name resolution failed with calls queued for config selection');
|
||||
}
|
||||
if (this.configSelector === null) {
|
||||
this.currentResolutionError = status;
|
||||
}
|
||||
const localQueue = this.configSelectionQueue;
|
||||
this.configSelectionQueue = [];
|
||||
this.callRefTimerUnref();
|
||||
|
@ -591,6 +604,9 @@ export class ChannelImplementation implements Channel {
|
|||
watcherObject.callback();
|
||||
}
|
||||
}
|
||||
if (newState !== ConnectivityState.TRANSIENT_FAILURE) {
|
||||
this.currentResolutionError = null;
|
||||
}
|
||||
}
|
||||
|
||||
private tryGetConfig(stream: Http2CallStream, metadata: Metadata) {
|
||||
|
@ -605,11 +621,15 @@ export class ChannelImplementation implements Channel {
|
|||
* ResolvingLoadBalancer may be idle and if so it needs to be kicked
|
||||
* because it now has a pending request. */
|
||||
this.resolvingLoadBalancer.exitIdle();
|
||||
this.configSelectionQueue.push({
|
||||
callStream: stream,
|
||||
callMetadata: metadata,
|
||||
});
|
||||
this.callRefTimerRef();
|
||||
if (this.currentResolutionError && !metadata.getOptions().waitForReady) {
|
||||
stream.cancelWithStatus(this.currentResolutionError.code, this.currentResolutionError.details);
|
||||
} else {
|
||||
this.configSelectionQueue.push({
|
||||
callStream: stream,
|
||||
callMetadata: metadata,
|
||||
});
|
||||
this.callRefTimerRef();
|
||||
}
|
||||
} else {
|
||||
const callConfig = this.configSelector(stream.getMethod(), metadata);
|
||||
if (callConfig.status === Status.OK) {
|
||||
|
|
|
@ -268,6 +268,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
|||
if (this.currentState === ConnectivityState.IDLE) {
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
}
|
||||
this.backoffTimeout.runOnce();
|
||||
}
|
||||
|
||||
private updateState(connectivityState: ConnectivityState, picker: Picker) {
|
||||
|
@ -294,18 +295,16 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
|||
);
|
||||
this.onFailedResolution(error);
|
||||
}
|
||||
this.backoffTimeout.runOnce();
|
||||
}
|
||||
|
||||
exitIdle() {
|
||||
this.childLoadBalancer.exitIdle();
|
||||
if (this.currentState === ConnectivityState.IDLE) {
|
||||
if (this.currentState === ConnectivityState.IDLE || this.currentState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||
if (this.backoffTimeout.isRunning()) {
|
||||
this.continueResolving = true;
|
||||
} else {
|
||||
this.updateResolution();
|
||||
}
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,4 +92,28 @@ describe('Client without a server', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Client with a nonexistent target domain', () => {
|
||||
let client: Client;
|
||||
before(() => {
|
||||
// DNS name that does not exist per RFC 6761 section 6.4
|
||||
client = new Client('host.invalid', clientInsecureCreds);
|
||||
});
|
||||
after(() => {
|
||||
client.close();
|
||||
});
|
||||
it('should fail multiple calls', function(done) {
|
||||
this.timeout(5000);
|
||||
// Regression test for https://github.com/grpc/grpc-node/issues/1411
|
||||
client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => {
|
||||
assert(error);
|
||||
assert.strictEqual(error?.code, grpc.status.UNAVAILABLE);
|
||||
client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => {
|
||||
assert(error);
|
||||
assert.strictEqual(error?.code, grpc.status.UNAVAILABLE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue