Skip to content

Conversation

cjihrig
Copy link
Contributor

@cjihrig cjihrig commented Jun 11, 2018

This is a work in progress attempt at a promisified DNS core module. I'm opening this early to see if people are generally interested in this approach. The entire API is not ported yet - mostly just missing lookup() and lookupService().

Some notes:

  • The new stuff is added to require('dns').promises, as similarly done in the fs API. Like in fs, they are lazy loaded and output an experimental warning.
  • There are currently no changes at the C++ layer because the DNS APIs come back to JavaScript to postprocess results before returning to user code.
  • This currently relies on process.binding('util').createPromise and friends, which are currently facing potential removal in another PR.
  • make test passed for me locally, so no regressions should exist in the callback based API.
Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot nodejs-github-bot added build Issues and PRs related to build files or the CI. dns Issues and PRs related to the dns subsystem. labels Jun 11, 2018
@vsemozhetbyt vsemozhetbyt added the promises Issues and PRs related to ECMAScript promises. label Jun 11, 2018
@devsnek
Copy link
Member

devsnek commented Jun 11, 2018

ideally we shouldn't be using those promise utils, they can't be optimised by v8

also should be looped into the new module naming thing as this can't be used first-class by esm

@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 11, 2018

ideally we shouldn't be using those promise utils, they can't be optimised by v8

OK, got rid of those.

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once tests and documentation are added :)

@cjihrig cjihrig force-pushed the dns branch 5 times, most recently from 26d49ec to 07345fe Compare June 12, 2018 21:16
@cjihrig cjihrig changed the title WIP: add promisified dns module dns: add promisified dns module Jun 12, 2018
@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 12, 2018

The full API has been ported, and docs+tests added.

@Trott Trott added the semver-minor PRs that contain new features and should be released in the next minor version. label Jun 12, 2018
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a sibling of the Resolver class, with Resolver.prototype.promises as its prototype?

@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 12, 2018

CI run: https://ci.nodejs.org/job/node-test-pull-request/15424/

Can we add a sibling of the Resolver class, with Resolver.prototype.promises as its prototype?

Do you mean like class PromiseResolver extends Resolver.prototype.promises (although that name is horrible :-))?

@addaleax
Copy link
Member

@cjihrig Yes, basically – I’d call it Resolver too, and put in on dns.promises.Resolver. :)

@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 14, 2018

@addaleax I added a dns.promises.Resolver class. I decided to get rid of Resolver.prototype.promises since there is a better way to access the functionality now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we seem to have two implicit "subclasses" of GetNameInfoReqWrap: one having a .callback property and is used for dns, while the other has .resolve and .reject properties. I would rather make this distinction explicit by having a GetAddrInfoPromiseReqWrap subclass that extends GetAddrInfoReqWrap.

Same for the other wrapper classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu I was aiming to avoid a lot of unnecessary duplicate code. The wraps behave exactly the same in the C++ layer.

Copy link
Member

@TimothyGu TimothyGu Jun 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjihrig Yeah I understand. What I'm trying to say is that you can have a JavaScript class that extends GetAddrInfoReqWrap. As long as its constructor calls super() properly it should work just fine. No duplication in C++ needed.

Copy link
Member

@joyeecheung joyeecheung Jun 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu It still seems a bit complex if we hang all the data (host, port, bindingName, etc.) onto a req wrap with C++ binding even though the C++ binding may not necessarily read those properties from the req wrap. The only property that matters to C++ should be wrap.oncomplete, so it may be cleaner if we create two JS classes, one for the callback and the other one for promises, each with a .handle (or a symbol property) pointing to those req wraps and add the data properties to the JS instances of those classes. Then the main difference between those two classes would just be how we call resolve/reject/callback(null, result)/callback(dnsException(...)) in handle.oncomplete. (That's basically what the two types of wraps do in the fs promises implementation although those are implemented in C++)

(It's a bit unfortunate that the resolves have a ChannelWrap that's not req wrap as _handle though)

Copy link
Member

@joyeecheung joyeecheung Jun 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought, it may not be feasible to separate the data properties to an outer object since we have been allowing users to retrieve them via the this bound to the callback...

EDIT: but we do not always bind the callback to the req wrap e.g. dns.lookup('127.0.0.1', function() { console.log(this) } gives you the global.

@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 15, 2018

doc/api/dns.md Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it say something like "the corresponding promises will be rejected with..."?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed.

Copy link
Contributor

@vsemozhetbyt vsemozhetbyt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some doc nits.

doc/api/dns.md Outdated
Copy link
Contributor

@vsemozhetbyt vsemozhetbyt Jun 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this should be ### level, not ##, to reflect the hierarchy of not-promisified API.

doc/api/dns.md Outdated
Copy link
Contributor

@vsemozhetbyt vsemozhetbyt Jun 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this should be #### level, not ###, to reflect the hierarchy of not-promisified API.

doc/api/dns.md Outdated

This comment was marked as resolved.

doc/api/dns.md Outdated

This comment was marked as resolved.

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`Promises` -> `Promise`s?

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a IPv4 addresses -> IPv4 addresses

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a IPv6 addresses -> IPv6 addresses

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing YAML.

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[`dns. -> [`dnsPromises. in all table (with new bottom references)?

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note needs to be promisified)

Copy link
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code wise LGTM, regarding the internal structure we can refactor on top of this PR later.

Copy link
Member

@joyeecheung joyeecheung Jun 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be renamed to something like CallbackResolver or LegacyResolver to be more clear?

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be [`resolver.setServers()`][`dnsPromises.setServers()`]?

doc/api/dns.md Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all these hashes, _callback part needs to be deleted.

doc/api/dns.md Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe show an example with async/await since according to polls that's what most users are doing?

This comment was marked as off-topic.

doc/api/dns.md Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This defacto introduces promise cancellation to Node.js - can we add a note to this that the API for cancellation may change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we can remove it and revisit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel(), getServers(), and setServers() don't technically use promises (or callbacks for that matter). They are just synchronous methods provided on the Resolver class that are included here too for completeness.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjihrig this is a promise cancellation API though since you are rejecting all promises with an error "from a distance".

I'd really prefer it if we took extra caution around it since we'll want one API for cancellation and ideally users can use the same one for say... promisified timers, fs and DNS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can avoid exposing this in the promise API, or rename it to something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm willing to remove it because it's a simple change. I don't agree with the change though, and I'm very -1 to renaming it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing it until we have a more general strategy about cancellation with async functions SGTM. We can bring this up with TC39 at the next meeting if you'd like.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really prefer it if this method dind't do so much explicit construction:

async function createLookupPromise(family, hostname, all, hints, verbatim) {
  if (!hostname) {
    if (all) return [];
   else 
     return { address: null, family: family === 6 ? 6 : 4 };
  }
  const matchedFamily = isIP(hostname);
  if (matchedFamily !== 0) {
    const result = { address: hostname, family: matchedFamily }
    if(all) return [result];
    return result;
  }
  return await new Promise((resolve, reject) => {
    const req = new GetAddrInfoReqWrap();
    // ...
    req.resolve = resolve;
  }
}

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of how the code is written and I think it can be simpler - but it looks correct and the effort and progress is really nice.

Left some comment and LGTMd the current code as experimental to iterate on :)

cjihrig added 2 commits June 20, 2018 13:35
PR-URL: nodejs#21264
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Because reasons.

PR-URL: nodejs#21264
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
@cjihrig cjihrig merged commit a77b30c into nodejs:master Jun 20, 2018
@cjihrig cjihrig deleted the dns branch June 20, 2018 17:47
@cjihrig
Copy link
Contributor Author

cjihrig commented Jun 20, 2018

Another CI run: https://ci.nodejs.org/job/node-test-pull-request/15536/ (AIX failures are unrelated, and I saw them on other CI runs today).

Landed in 7486c4d, with the cancel() removal in a77b30c (so it would make for an easy git revert).

targos pushed a commit that referenced this pull request Jun 20, 2018
PR-URL: #21264
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
targos pushed a commit that referenced this pull request Jun 20, 2018
Because reasons.

PR-URL: #21264
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
@benjamingr
Copy link
Member

Awesome, going to follow up with the nits from above :)

@targos targos mentioned this pull request Jul 3, 2018
targos added a commit that referenced this pull request Jul 3, 2018
Notable changes:

* build:
  * Node.js should now be about 60% faster to startup than the previous version,
    thanks to the use V8's code cache feature for core modules. [#21405](#21405)
* dns:
  * An experimental promisified version of the dns module is now available. Give
    it a try with `require('dns').promises`. [#21264](#21264)
* fs:
  * `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
  * `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
    specification ([reference](tc39/ecma262#1220)).
    Since Node.js now has experimental support for worker threads, we are being
    proactive and added a `notify` alias, while emitting a warning if
    `wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
  * Add API for asynchronous functions. [#17887](#17887)
* util:
  * `util.inspect` is now able to return a result instead of throwing when the
    maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
  * Add `script.createCachedData()`. This API replaces the `produceCachedData`
    option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
  * Support for relative paths has been added to the `Worker` constructor. Paths
    are interpreted relative to the current working directory. [#21407](#21407)

PR-URL: #21629
targos added a commit that referenced this pull request Jul 4, 2018
Notable changes:

* dns:
  * An experimental promisified version of the dns module is now available. Give
    it a try with `require('dns').promises`. [#21264](#21264)
* fs:
  * `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
  * `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
    specification ([reference](tc39/ecma262#1220)).
    Since Node.js now has experimental support for worker threads, we are being
    proactive and added a `notify` alias, while emitting a warning if
    `wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
  * Add API for asynchronous functions. [#17887](#17887)
* util:
  * `util.inspect` is now able to return a result instead of throwing when the
    maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
  * Add `script.createCachedData()`. This API replaces the `produceCachedData`
    option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
  * Support for relative paths has been added to the `Worker` constructor. Paths
    are interpreted relative to the current working directory. [#21407](#21407)

PR-URL: #21629
targos added a commit that referenced this pull request Jul 4, 2018
Notable changes:

* dns:
  * An experimental promisified version of the dns module is now available. Give
    it a try with `require('dns').promises`. [#21264](#21264)
* fs:
  * `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
  * `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
    specification ([reference](tc39/ecma262#1220)).
    Since Node.js now has experimental support for worker threads, we are being
    proactive and added a `notify` alias, while emitting a warning if
    `wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
  * Add API for asynchronous functions. [#17887](#17887)
* util:
  * `util.inspect` is now able to return a result instead of throwing when the
    maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
  * Add `script.createCachedData()`. This API replaces the `produceCachedData`
    option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
  * Support for relative paths has been added to the `Worker` constructor. Paths
    are interpreted relative to the current working directory. [#21407](#21407)

PR-URL: #21629
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Issues and PRs related to build files or the CI. dns Issues and PRs related to the dns subsystem. promises Issues and PRs related to ECMAScript promises. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.