Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dns.setServers doesn't play well with async code #1071

Closed
silverwind opened this issue Mar 5, 2015 · 14 comments
Closed

dns.setServers doesn't play well with async code #1071

silverwind opened this issue Mar 5, 2015 · 14 comments
Labels
confirmed-bug Issues with confirmed bugs. dns Issues and PRs related to the dns subsystem. stalled Issues and PRs that are stalled.

Comments

@silverwind
Copy link
Contributor

nodejs/node-v0.x-archive#9243 (comment) led me to realizing that the way dns.setServers is implemented doesn't play too well with async code.

The current target server is stored in a per-app global state, which would probably be fine with sync code, but complicates things if you want to query multiple servers with our async resolve.

I'd propose adding an options object to resolve containing servers, while deprecating dns.setServers in a major version. I think having an option object on resolve methods could prove useful later.

Note: I haven't checked if c-ares enables per-query target servers easily, maybe @indutny can comment on that part.

@indutny
Copy link
Member

indutny commented Mar 5, 2015

Hm... this is not a bad suggestion at all, but I this is not the way c-ares works :) The thing that you propose will require creating and keeping alive separate channels in a hashmap.

@silverwind
Copy link
Contributor Author

Ah, so there is a reason it's implemented in this way.

Maybe something that should be kept in mind for #1013.

@piscisaureus
Copy link
Contributor

Another solution would be to allow multiple "resolver" instances, e.g. support the following pattern:

var resolver = dns.createResolver(server);
resolver.lookup(...);
resolver.close();

The default lookup method would just delegate to a default resolver.

@indutny
Copy link
Member

indutny commented Mar 5, 2015

Please don't close this, I didn't mean that it wasn't feasible :) Just wanted to stress out how it will be implemented.

@silverwind silverwind reopened this Mar 5, 2015
@silverwind
Copy link
Contributor Author

"resolver" instances

This is what node-dns basically does. It's an option, but I think I'm leaning more towards the option object for simplicity.

@silverwind silverwind changed the title Deprecate dns.setServers - Replace it with an option on resolve dns.setServers doesn't play well with async code Mar 5, 2015
@ChALkeR
Copy link
Member

ChALkeR commented Mar 5, 2015

Atm, dns.setServers asserts when there are running dns queries or callbacks of dns queries.

This crashes:

var dns = require('dns');

var servers = dns.getServers();
dns.setServers(['208.67.222.222', '208.67.220.220']);
dns.resolve4('myip.opendns.com', function(err, addresses, family) {
    dns.setServers(servers);
    console.log('addresses:', addresses);
});

This works:

var dns = require('dns');

var servers = dns.getServers();
dns.setServers(['208.67.222.222', '208.67.220.220']);
dns.resolve4('myip.opendns.com', function(err, addresses, family) {
    setTimeout(function() { dns.setServers(servers); }, 0);
    console.log('addresses:', addresses);
});

@silverwind
Copy link
Contributor Author

@ChALkeR this is known, see #894.

@brendanashworth brendanashworth added the dns Issues and PRs related to the dns subsystem. label Mar 6, 2015
This was referenced Mar 10, 2015
@snsparrish
Copy link

I hope this idea hasn't died. I'd really like to see this functionality. The node-dns alternative doesn't appear to be functioning at all in 2.2.1.

@silverwind
Copy link
Contributor Author

I have a change for this i mind after the new DNS implementation lands (#1843 (comment))

@Trott Trott added the stalled Issues and PRs that are stalled. label Mar 11, 2016
@Trott
Copy link
Member

Trott commented Mar 11, 2016

I'm going to add a stalled label until #1843 comes together. Feel free to remove if that's an abuse of that particular label.

@jasnell
Copy link
Member

jasnell commented Apr 19, 2016

It looks like #1843 might be stalling out (@mscdex). Thinking about this a bit, I'm wondering if a possible stop-gap "solution" would be to modify setServers() such that it will throw early if there are any pending resolves that have not yet returned. Ultimately I think the right solution is to allow multiple resolvers as @piscisaureus has suggested but that's a larger change. If we did go that route, setServers() would be deprecated anyway. Adding the throw would seem to be better than allowing the current existing failure.

@silverwind
Copy link
Contributor Author

silverwind commented Apr 19, 2016

@jasnell I'm all for it if it avoids the crash. It would give the user a chance to workaround.

Regarding alternatives, @mafintosh's dns-socket is a pretty flexible JS DNS solution I've been using recently. Maybe worth investigating how its perf compares to @mscdex's implementation.

@jasnell jasnell mentioned this issue Apr 19, 2016
4 tasks
jasnell added a commit to jasnell/node that referenced this issue Apr 19, 2016
This seeks to address issue nodejs#1071
in a couple of ways:

1. An internal ref counter is used to count the number of outstanding
   dns.resolve() requests are still pending. If this number is > 0
   when dns.setServers() is called, an error will be thrown.

2. dns.resolve() will now return an EventEmitter that can emit three
   possible events: `'error'`, `'resolve'` and `'complete'`.

Previously, if there were any errors reported while *setting up* the
dns.resolve operation, they would be thrown immediately. However, any
errors that occur *during* the dns.operation would be passed to the
callback function as the first argument. Now, all errors are routed
through the `'error'` event on the EventEmitter. This makes the error
handling more consistent but changes the flow since `dns.resolve*()`
will no longer throw immediately.

If a callback is passed to `dns.resolve*()`, which is the current
usage pattern, it is set as the handler for **both** the `'resolve'`
and `'error'` events.

Alternatively, it is now possible to omit the callback and add
listeners directly to the EventEmitter returned by dns.resolve*().
The `'resolve'` listener is passed *only* the results of the
resolve (errors are passed to `'error'`).

So, for example, you can continue to do this:

```js
dns.resolve('www.example.org', 'A', (err, addresses) => {
  if (err) { /** ... **/ }
  // do stuff
});
```

Or you can do:

```js
dns.resolve('www.example.org', 'A')
   .on('resolve', (addresses) => {
     // do stuff
   })
   .on('error', (error) => {
     // handle it
   })
   .on('complete', () => {
     // do this here because otherwise it'll throw.
     dns.setServers([]);
   }).
```

On the `dns.setServers()` part, the `'complete'` event is emitted using
`setImmediate()`. This ensures that the event is not emitted until after
c-ares has an opportunity to clean up. This avoids the assert that brings
down the Node process if setServers is called at the wrong time.
@jasnell jasnell added the confirmed-bug Issues with confirmed bugs. label Apr 22, 2016
@XadillaX
Copy link
Contributor

Hey @silverwind, does #13050 fix this?

@jasnell
Copy link
Member

jasnell commented May 30, 2017

Yes, I believe #13050 actually does fix this. Good catch. There are still a number of ways that the dns module can be improved, but the change in #13050 addresses the immediate issue. Closing. In any of the collaborators feel that it's necessary to do so, we can reopen the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug Issues with confirmed bugs. dns Issues and PRs related to the dns subsystem. stalled Issues and PRs that are stalled.
Projects
None yet
Development

No branches or pull requests

9 participants