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