Skip to content

Commit

Permalink
grpc-js: Add support for grpc.dns_min_time_between_resolutions_ms cha…
Browse files Browse the repository at this point in the history
…nnel arg
  • Loading branch information
murgatroid99 committed Apr 1, 2022
1 parent a993703 commit 8a50303
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/grpc-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`.
- `grpc.enable_http_proxy`
- `grpc.default_compression_algorithm`
- `grpc.enable_channelz`
- `grpc.dns_min_time_between_resolutions_ms`
- `grpc-node.max_session_memory`
- `channelOverride`
- `channelFactoryOverride`
Expand Down
2 changes: 1 addition & 1 deletion packages/grpc-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grpc/grpc-js",
"version": "1.6.0",
"version": "1.6.1",
"description": "gRPC Library for Node - pure JS implementation",
"homepage": "https://grpc.io/",
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
Expand Down
2 changes: 2 additions & 0 deletions packages/grpc-js/src/channel-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ChannelOptions {
'grpc.http_connect_creds'?: string;
'grpc.default_compression_algorithm'?: CompressionAlgorithms;
'grpc.enable_channelz'?: number;
'grpc.dns_min_time_between_resolutions_ms'?: number;
'grpc-node.max_session_memory'?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
Expand All @@ -69,6 +70,7 @@ export const recognizedOptions = {
'grpc.max_receive_message_length': true,
'grpc.enable_http_proxy': true,
'grpc.enable_channelz': true,
'grpc.dns_min_time_between_resolutions_ms': true,
'grpc-node.max_session_memory': true,
};

Expand Down
45 changes: 39 additions & 6 deletions packages/grpc-js/src/resolver-dns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ function trace(text: string): void {
*/
const DEFAULT_PORT = 443;

const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000;

const resolveTxtPromise = util.promisify(dns.resolveTxt);
const dnsLookupPromise = util.promisify(dns.lookup);

Expand Down Expand Up @@ -79,6 +81,12 @@ class DnsResolver implements Resolver {
private readonly ipResult: SubchannelAddress[] | null;
private readonly dnsHostname: string | null;
private readonly port: number | null;
/**
* Minimum time between resolutions, measured as the time between starting
* successive resolution requests. Only applies to successful resolutions.
* Failures are handled by the backoff timer.
*/
private readonly minTimeBetweenResolutionsMs: number;
private pendingLookupPromise: Promise<dns.LookupAddress[]> | null = null;
private pendingTxtPromise: Promise<string[][]> | null = null;
private latestLookupResult: TcpSubchannelAddress[] | null = null;
Expand All @@ -88,6 +96,8 @@ class DnsResolver implements Resolver {
private defaultResolutionError: StatusObject;
private backoff: BackoffTimeout;
private continueResolving = false;
private nextResolutionTimer: NodeJS.Timer;
private isNextResolutionTimerRunning = false;
constructor(
private target: GrpcUri,
private listener: ResolverListener,
Expand Down Expand Up @@ -134,6 +144,10 @@ class DnsResolver implements Resolver {
}
}, backoffOptions);
this.backoff.unref();

this.minTimeBetweenResolutionsMs = channelOptions['grpc.dns_min_time_between_resolutions_ms'] ?? DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS;
this.nextResolutionTimer = setTimeout(() => {}, 0);
clearTimeout(this.nextResolutionTimer);
}

/**
Expand Down Expand Up @@ -183,6 +197,7 @@ class DnsResolver implements Resolver {
(addressList) => {
this.pendingLookupPromise = null;
this.backoff.reset();
this.backoff.stop();
const ip4Addresses: dns.LookupAddress[] = addressList.filter(
(addr) => addr.family === 4
);
Expand Down Expand Up @@ -229,6 +244,7 @@ class DnsResolver implements Resolver {
(err as Error).message
);
this.pendingLookupPromise = null;
this.stopNextResolutionTimer();
this.listener.onError(this.defaultResolutionError);
}
);
Expand Down Expand Up @@ -282,17 +298,34 @@ class DnsResolver implements Resolver {
}
}

private startNextResolutionTimer() {
this.nextResolutionTimer = setTimeout(() => {
this.stopNextResolutionTimer();
if (this.continueResolving) {
this.startResolutionWithBackoff();
}
}, this.minTimeBetweenResolutionsMs).unref?.();
this.isNextResolutionTimerRunning = true;
}

private stopNextResolutionTimer() {
clearTimeout(this.nextResolutionTimer);
this.isNextResolutionTimerRunning = false;
}

private startResolutionWithBackoff() {
this.startResolution();
this.backoff.runOnce();
this.startNextResolutionTimer();
}

updateResolution() {
/* If there is a pending lookup, just let it finish. Otherwise, if the
* backoff timer is running, do another lookup when it ends, and if not,
* do another lookup immeidately. */
* nextResolutionTimer or backoff timer is running, set the
* continueResolving flag to resolve when whichever of those timers
* fires. Otherwise, start resolving immediately. */
if (this.pendingLookupPromise === null) {
if (this.backoff.isRunning()) {
if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) {
this.continueResolving = true;
} else {
this.startResolutionWithBackoff();
Expand All @@ -301,9 +334,9 @@ class DnsResolver implements Resolver {
}

destroy() {
/* Do nothing. There is not a practical way to cancel in-flight DNS
* requests, and after this function is called we can expect that
* updateResolution will not be called again. */
this.continueResolving = false;
this.backoff.stop();
this.stopNextResolutionTimer();
}

/**
Expand Down

0 comments on commit 8a50303

Please sign in to comment.