GeezerVison is showing github.com — dns: support max timeout · nodejs/node@c52aaac
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/dns.md +2Lines changed: 2 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -157,6 +157,8 @@ Create a new resolver. | 157 | 157 | default timeout. | 158 | 158 | * `tries` {integer} The number of tries the resolver will try contacting | 159 | 159 | each name server before giving up. **Default:** `4` | | 160 | + * `maxTimeout` {integer} The max retry timeout, in milliseconds. | | 161 | + **Default:** `0`, disabled. | 160 | 162 |
| 161 | 163 | ### `resolver.cancel()` | 162 | 164 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/dns/utils.js +14-6Lines changed: 14 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -25,6 +25,7 @@ const { | 25 | 25 | validateInt32, | 26 | 26 | validateOneOf, | 27 | 27 | validateString, | | 28 | + validateUint32, | 28 | 29 | } = require('internal/validators'); | 29 | 30 | let binding; | 30 | 31 | function lazyBinding() { | @@ -49,6 +50,12 @@ function validateTimeout(options) { | 49 | 50 | return timeout; | 50 | 51 | } | 51 | 52 |
| | 53 | +function validateMaxTimeout(options) { | | 54 | + const { maxTimeout = 0 } = { ...options }; | | 55 | + validateUint32(maxTimeout, 'options.maxTimeout'); | | 56 | + return maxTimeout; | | 57 | +} | | 58 | + | 52 | 59 | function validateTries(options) { | 53 | 60 | const { tries = 4 } = { ...options }; | 54 | 61 | validateInt32(tries, 'options.tries', 1); | @@ -67,17 +74,18 @@ class ResolverBase { | 67 | 74 | constructor(options = undefined) { | 68 | 75 | const timeout = validateTimeout(options); | 69 | 76 | const tries = validateTries(options); | | 77 | + const maxTimeout = validateMaxTimeout(options); | 70 | 78 | // If we are building snapshot, save the states of the resolver along | 71 | 79 | // the way. | 72 | 80 | if (isBuildingSnapshot()) { | 73 | | - this[kSnapshotStates] = { timeout, tries }; | | 81 | + this[kSnapshotStates] = { timeout, tries, maxTimeout }; | 74 | 82 | } | 75 | | - this[kInitializeHandle](timeout, tries); | | 83 | + this[kInitializeHandle](timeout, tries, maxTimeout); | 76 | 84 | } | 77 | 85 |
| 78 | | - [kInitializeHandle](timeout, tries) { | | 86 | + [kInitializeHandle](timeout, tries, maxTimeout) { | 79 | 87 | const { ChannelWrap } = lazyBinding(); | 80 | | - this._handle = new ChannelWrap(timeout, tries); | | 88 | + this._handle = new ChannelWrap(timeout, tries, maxTimeout); | 81 | 89 | } | 82 | 90 |
| 83 | 91 | cancel() { | @@ -187,8 +195,8 @@ class ResolverBase { | 187 | 195 | } | 188 | 196 |
| 189 | 197 | [kDeserializeResolver]() { | 190 | | - const { timeout, tries, localAddress, servers } = this[kSnapshotStates]; | 191 | | - this[kInitializeHandle](timeout, tries); | | 198 | + const { timeout, tries, maxTimeout, localAddress, servers } = this[kSnapshotStates]; | | 199 | + this[kInitializeHandle](timeout, tries, maxTimeout); | 192 | 200 | if (localAddress) { | 193 | 201 | const { ipv4, ipv6 } = localAddress; | 194 | 202 | this._handle.setLocalAddress(ipv4, ipv6); | |
Collapse file Copy file name to clipboardExpand all lines: src/cares_wrap.cc +19-11Lines changed: 19 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -787,14 +787,15 @@ Maybe<int> ParseSoaReply(Environment* env, | 787 | 787 | } | 788 | 788 | } // anonymous namespace | 789 | 789 |
| 790 | | -ChannelWrap::ChannelWrap( | 791 | | - Environment* env, | 792 | | - Local<Object> object, | 793 | | - int timeout, | 794 | | - int tries) | | 790 | +ChannelWrap::ChannelWrap(Environment* env, | | 791 | + Local<Object> object, | | 792 | + int timeout, | | 793 | + int tries, | | 794 | + int max_timeout) | 795 | 795 | : AsyncWrap(env, object, PROVIDER_DNSCHANNEL), | 796 | 796 | timeout_(timeout), | 797 | | - tries_(tries) { | | 797 | + tries_(tries), | | 798 | + max_timeout_(max_timeout) { | 798 | 799 | MakeWeak(); | 799 | 800 |
| 800 | 801 | Setup(); | @@ -808,13 +809,15 @@ void ChannelWrap::MemoryInfo(MemoryTracker* tracker) const { | 808 | 809 |
| 809 | 810 | void ChannelWrap::New(const FunctionCallbackInfo<Value>& args) { | 810 | 811 | CHECK(args.IsConstructCall()); | 811 | | - CHECK_EQ(args.Length(), 2); | | 812 | + CHECK_EQ(args.Length(), 3); | 812 | 813 | CHECK(args[0]->IsInt32()); | 813 | 814 | CHECK(args[1]->IsInt32()); | | 815 | + CHECK(args[2]->IsInt32()); | 814 | 816 | const int timeout = args[0].As<Int32>()->Value(); | 815 | 817 | const int tries = args[1].As<Int32>()->Value(); | | 818 | + const int max_timeout = args[2].As<Int32>()->Value(); | 816 | 819 | Environment* env = Environment::GetCurrent(args); | 817 | | - new ChannelWrap(env, args.This(), timeout, tries); | | 820 | + new ChannelWrap(env, args.This(), timeout, tries, max_timeout); | 818 | 821 | } | 819 | 822 |
| 820 | 823 | GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env, | @@ -879,9 +882,14 @@ void ChannelWrap::Setup() { | 879 | 882 | } | 880 | 883 |
| 881 | 884 | /* We do the call to ares_init_option for caller. */ | 882 | | - const int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | | 883 | | - ARES_OPT_SOCK_STATE_CB | ARES_OPT_TRIES | | 884 | | - ARES_OPT_QUERY_CACHE; | | 885 | + int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB | | | 886 | + ARES_OPT_TRIES | ARES_OPT_QUERY_CACHE; | | 887 | + | | 888 | + if (max_timeout_ > 0) { | | 889 | + options.maxtimeout = max_timeout_; | | 890 | + optmask |= ARES_OPT_MAXTIMEOUTMS; | | 891 | + } | | 892 | + | 885 | 893 | r = ares_init_options(&channel_, &options, optmask); | 886 | 894 |
| 887 | 895 | if (r != ARES_SUCCESS) { | |
Collapse file Copy file name to clipboardExpand all lines: src/cares_wrap.h +6-5Lines changed: 6 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -151,11 +151,11 @@ struct NodeAresTask final : public MemoryRetainer { | 151 | 151 |
| 152 | 152 | class ChannelWrap final : public AsyncWrap { | 153 | 153 | public: | 154 | | - ChannelWrap( | 155 | | - Environment* env, | 156 | | - v8::Local<v8::Object> object, | 157 | | - int timeout, | 158 | | - int tries); | | 154 | + ChannelWrap(Environment* env, | | 155 | + v8::Local<v8::Object> object, | | 156 | + int timeout, | | 157 | + int tries, | | 158 | + int max_timeout); | 159 | 159 | ~ChannelWrap() override; | 160 | 160 |
| 161 | 161 | static void New(const v8::FunctionCallbackInfo<v8::Value>& args); | @@ -190,6 +190,7 @@ class ChannelWrap final : public AsyncWrap { | 190 | 190 | bool library_inited_ = false; | 191 | 191 | int timeout_; | 192 | 192 | int tries_; | | 193 | + int max_timeout_; | 193 | 194 | int active_query_count_ = 0; | 194 | 195 | NodeAresTask::List task_list_; | 195 | 196 | }; | |
Collapse file Copy file name to clipboard +77Lines changed: 77 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +const common = require('../common'); | | 3 | +const dnstools = require('../common/dns'); | | 4 | +const dns = require('dns'); | | 5 | +const assert = require('assert'); | | 6 | +const dgram = require('dgram'); | | 7 | + | | 8 | +[ | | 9 | + -1, | | 10 | + 1.1, | | 11 | + NaN, | | 12 | + undefined, | | 13 | + {}, | | 14 | + [], | | 15 | + null, | | 16 | + function() {}, | | 17 | + Symbol(), | | 18 | + true, | | 19 | + Infinity, | | 20 | +].forEach((maxTimeout) => { | | 21 | + try { | | 22 | + new dns.Resolver({ maxTimeout }); | | 23 | + } catch (e) { | | 24 | + assert.ok(/ERR_OUT_OF_RANGE|ERR_INVALID_ARG_TYPE/i.test(e.code)); | | 25 | + } | | 26 | +}); | | 27 | + | | 28 | +const server = dgram.createSocket('udp4'); | | 29 | +const nxdomain = 'nxdomain.org'; | | 30 | +const domain = 'example.org'; | | 31 | +const answers = [{ type: 'A', address: '1.2.3.4', ttl: 123, domain }]; | | 32 | + | | 33 | +server.on('message', common.mustCallAtLeast((msg, { address, port }) => { | | 34 | + const parsed = dnstools.parseDNSPacket(msg); | | 35 | + if (parsed.questions[0].domain === nxdomain) { | | 36 | + return; | | 37 | + } | | 38 | + assert.strictEqual(parsed.questions[0].domain, domain); | | 39 | + server.send(dnstools.writeDNSPacket({ | | 40 | + id: parsed.id, | | 41 | + questions: parsed.questions, | | 42 | + answers: answers, | | 43 | + }), port, address); | | 44 | +}), 1); | | 45 | + | | 46 | +server.bind(0, common.mustCall(async () => { | | 47 | + const address = server.address(); | | 48 | + // Test if the Resolver works as before. | | 49 | + const resolver = new dns.promises.Resolver({ timeout: 1000, tries: 1, maxTimeout: 1000 }); | | 50 | + resolver.setServers([`127.0.0.1:${address.port}`]); | | 51 | + const res = await resolver.resolveAny('example.org'); | | 52 | + assert.strictEqual(res.length, 1); | | 53 | + assert.strictEqual(res.length, answers.length); | | 54 | + assert.strictEqual(res[0].address, answers[0].address); | | 55 | + | | 56 | + // Test that maxTimeout is effective. | | 57 | + // Without maxTimeout, the timeout will keep increasing when retrying. | | 58 | + const timeout1 = await timeout(address, { timeout: 500, tries: 3 }); | | 59 | + // With maxTimeout, the timeout will always be 500 when retrying. | | 60 | + const timeout2 = await timeout(address, { timeout: 500, tries: 3, maxTimeout: 500 }); | | 61 | + console.log(`timeout1: ${timeout1}, timeout2: ${timeout2}`); | | 62 | + assert.strictEqual(timeout1 !== undefined && timeout2 !== undefined, true); | | 63 | + assert.strictEqual(timeout1 > timeout2, true); | | 64 | + server.close(); | | 65 | +})); | | 66 | + | | 67 | +async function timeout(address, options) { | | 68 | + const start = Date.now(); | | 69 | + const resolver = new dns.promises.Resolver(options); | | 70 | + resolver.setServers([`127.0.0.1:${address.port}`]); | | 71 | + try { | | 72 | + await resolver.resolveAny(nxdomain); | | 73 | + } catch (e) { | | 74 | + assert.strictEqual(e.code, 'ETIMEOUT'); | | 75 | + return Date.now() - start; | | 76 | + } | | 77 | +} |
|