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

module: resolve _-prefixed dependencies to real path #6460

Closed
wants to merge 5 commits into from

Conversation

alexanderGugel
Copy link

@alexanderGugel alexanderGugel commented Apr 29, 2016

Checklist
  • tests and code linting passes
  • a test and/or benchmark is included
  • documentation is changed or added
  • the commit message follows commit guidelines
Affected core subsystem(s)
  • module (require())
Description of change

Since #5950, require('some-symlink') no longer resolves the module to its
real path, but instead uses the path of the symlink itself as a cache key.

This change allows users to resolve dependencies to their real path when
required using the _-prefix.

Example using old mechanism (pre v6.0.0):

node_modules/_real-module -> ../real-module
real-modules/index.js
require('./real_module') === require('real_module')

Example using new mechanism (post v6.0.0):

node_modules/real-module -> ../real-module
real-modules/index.js
require('./real_module') !== require('real_module')

As discussed in #3402

@MylesBorins MylesBorins added the module Issues and PRs related to the module subsystem. label Apr 29, 2016
@@ -144,52 +182,24 @@ Module._findPath = function(request, paths, isMain) {
return Module._pathCache[cacheKey];
}

var exts;
const exts = Object.keys(Module._extensions);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was the lazy-loading of the extensions removed?

Copy link
Author

Choose a reason for hiding this comment

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

Simplified the control flow, since tryFindPath was split out in order to reduce redundancy (just like readFilePath).

More than happy to add it back in, but seemed like a very marginal cost to me.

@MylesBorins
Copy link
Contributor

@mscdex
Copy link
Contributor

mscdex commented Apr 29, 2016

Looks like it's causing test-cwd-enoent-preload.js to fail just about everywhere.

@mscdex
Copy link
Contributor

mscdex commented Apr 29, 2016

Despite the test failure, I ran the module loader benchmark before and after this change (and after fixing a const causing an aborted optimization):

module/module-loader.js thousands=50 fullPath=true: ./node-new: 9.8836 ./node-old: 11.831 .. -16.46%
module/module-loader.js thousands=50 fullPath=false: ./node-new: 8.3574 ./node-old: 9.4516 . -11.58%

@alexanderGugel
Copy link
Author

@mscdex @thealphanerd Fixed.

@alexanderGugel
Copy link
Author

alexanderGugel commented Apr 29, 2016

@mscdex Yes, this is expected, since now we need to check if the prefixed symlink exists for every required module. I don't think there is any way around in any of the proposed solution.

Update Actually there is, but is means the precedence changes (default to new behaviour if both files exist).

// If _basePath is a symlink, use the real path rather than the path of the
// symlink as cache key.
const _basePath = path.resolve(curPath, '/_' + request);
const basePath = path.resolve(curPath, request);
Copy link
Contributor

Choose a reason for hiding this comment

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

The above two lines are contributing to v8 aborting optimization of Module._findPath() because of the const usage. They should be reverted to var for now.

@jasnell
Copy link
Member

jasnell commented Apr 29, 2016

@alexanderGugel ... Thank you very much for this. I'm still skeptical if this is the right approach. As an interim solution, I'm currently leaning towards a flag based fix that conditionally reverts the change that was made with the long term solution being closer to the two cache model I described. I'll have some time to walk through this PR in detail soon tho. Really appreciate you putting this together tho!

Since nodejs#5950, `require('some-symlink')` no longer resolves the module to its
real path, but instead uses the path of the symlink itself as a cache key.

This change allows users to resolve dependencies to their real path when
required using the _-prefix.

Example using old mechanism (pre v6.0.0):
---

node_modules/_real-module -> ../real-module
real-modules/index.js

require('./real_module') === require('real_module')

Example using new mechanism (post v6.0.0):
---

node_modules/real-module -> ../real-module
real-modules/index.js

require('./real_module') !== require('real_module')

As discussed in nodejs#3402
@alexanderGugel
Copy link
Author

Sorry, was too fast. Didn't actually fix the process.cwd() issue.

That way stat() only needs to be called once in most cases (assuming non
_-prefixed symlinks are more common).
@alexanderGugel
Copy link
Author

alexanderGugel commented Apr 29, 2016

@mscdex How can I run those benchmarks? 37b7b5e should fix the perf issue.

This also makes this change mostly backwards compatible with the symlink change introduced in v6.0.0.

// If _basePath is a symlink, use the real path rather than the path of the
// symlink as cache key.
const _basePath = path.resolve(curPath, '_' + request);
const basePath = path.resolve(curPath, request);
Copy link
Contributor

Choose a reason for hiding this comment

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

The above two lines need to be changed to use var instead of const to avoid aborted optimziations of _findPath() by v8.

Copy link
Author

@alexanderGugel alexanderGugel Apr 29, 2016

Choose a reason for hiding this comment

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

fixed.

const in for-loop can not be optimized.
* Fixes `test-cwd-enoent-preload.js`
* Minor performance improvement
@@ -190,15 +190,15 @@ Module._findPath = function(request, paths, isMain) {
// For each path
for (var i = 0; i < paths.length; i++) {
// Don't search further if path doesn't exist
const curPath = paths[i];
var curPath = paths[i];
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, this line was fine. It was just the other 3 that were causing problems.

@alexanderGugel
Copy link
Author

alexanderGugel commented Apr 29, 2016

@mscdex Now fixed test-cwd-enoent-preload.js (ca804f5).

@mscdex
Copy link
Contributor

mscdex commented Apr 29, 2016

@alexanderGugel Assuming node-old is a copy of the binary representing node before this PR, you'd do something like: node benchmark/compare -r -g ./node ./node-old -- module module-loader

With the most recent changes I now see:

module/module-loader.js thousands=50 fullPath=true: ./node: 11.377 ./node-old: 11.781 . -3.43%
module/module-loader.js thousands=50 fullPath=false: ./node: 9.4919 ./node-old: 9.504 . -0.13%

@alexanderGugel
Copy link
Author

alexanderGugel commented Apr 29, 2016

@mscdex Thanks. I would assume the performance regression is most likely because of functions not being optimised rather than unneeded syscalls, since the benchmarks most likely don't even cover those.

@mscdex
Copy link
Contributor

mscdex commented Apr 29, 2016

@alexanderGugel Can you explain what you mean by "not being optimized?"

@alexanderGugel
Copy link
Author

alexanderGugel commented Apr 29, 2016

37b7b5e means there are no additional stats when the module can be found via a non _-prefixed linkname, meaning that the only scenario in which an additional stat is needed is when the module could not be found, and we need to check if a _-prefixed version exists.

Going to bed now.

@mscdex
Copy link
Contributor

mscdex commented Apr 29, 2016

I will look into additional optimizations

Ensure that `require('some-module/index.js')` is being resolved properly when
`some-module` is a _-prefixed symlink.
@alexanderGugel
Copy link
Author

@mscdex Any update on the optimizations? Or is ~ 3.4 % in the acceptable range?

Also there seems to be a discussion going on about whether or not the module API is actually locked. #6528 - This will probably affect this PR.

@MylesBorins
Copy link
Contributor

@mscdex
Copy link
Contributor

mscdex commented May 2, 2016

@alexanderGugel I haven't had time yet unfortunately. I will try to dig into it this week though.

@alexanderGugel
Copy link
Author

It looks like the commit that broke this in the first place is about to be reverted, which would render this PR obsolete (#6537).

So maybe the default should be changed... use the realpath of symlinks by default and use the path of the symlink if the linkname starts with _ - so exactly the other way around.

That way backwards compatibility with v5.0.0 could be preserved while adding the ability to conditionally use the v6.0.0 mechanism (= path of symlink).

@dlongley
Copy link

dlongley commented May 3, 2016

@alexanderGugel,

That way backwards compatibility with v5.0.0 could be preserved while adding the ability to conditionally use the v6.0.0 mechanism (= path of symlink).

+1 as a possible way to address the peer dependency bug. However, we should explore other ideas as well.

@MylesBorins
Copy link
Contributor

Since we reverted to the old behavior and put the new behavior behind a flag is it safe to close this?

@alexanderGugel
Copy link
Author

Yup, although there is still no way to have both solutions.

@dlongley
Copy link

dlongley commented Jun 7, 2016

We still need a way get both behaviors working without a flag -- but I don't think this option was going to work (at least not on its own).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module Issues and PRs related to the module subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants