-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Fix perf regression #6204
Fix perf regression #6204
Changes from all commits
93fe16c
d27b8cd
c0ff7cf
f6a17f7
324ec4e
30542c4
29571d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import * as reporters from '../../../src/reporters/index.js'; | |
import {Install, run as install} from '../../../src/cli/commands/install.js'; | ||
import Lockfile from '../../../src/lockfile'; | ||
import * as fs from '../../../src/util/fs.js'; | ||
import * as misc from '../../../src/util/misc.js'; | ||
import {getPackageVersion, explodeLockfile, runInstall, runLink, createLockfile, run as buildRun} from '../_helpers.js'; | ||
|
||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000; | ||
|
@@ -111,6 +112,20 @@ test('installing a package with a renamed file should not delete it', () => | |
expect(await fs.exists(`${config.cwd}/node_modules/pkg/state.js`)).toEqual(true); | ||
})); | ||
|
||
test("installing a new package should correctly update it, even if the files mtime didn't change", () => | ||
runInstall({}, 'mtime-same', async (config, reporter): Promise<void> => { | ||
await misc.sleep(2000); | ||
|
||
const pkgJson = await fs.readJson(`${config.cwd}/package.json`); | ||
pkgJson.dependencies['pkg'] = 'file:./pkg-b.tgz'; | ||
await fs.writeFile(`${config.cwd}/package.json`, JSON.stringify(pkgJson)); | ||
|
||
const reInstall = new Install({}, config, reporter, await Lockfile.fromDirectory(config.cwd)); | ||
await reInstall.init(); | ||
|
||
expect(await fs.readJson(`${config.cwd}/node_modules/pkg/package.json`)).toMatchObject({version: '2.0.0'}); | ||
})); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test still passes if I revert the changes (speficially in util/fs). Is this a local/os issue for me? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a test that ensures that #5723 is still fixed. #6010 was missing tests, so I wanted to be sure that the initial bug wasn't coming back after replacing the commit that was meant to fix it. There is no test for the perf regression since I'm not sure we can make one that wouldn't be super-flaky 😞 To give you an idea, the different of install time on my machine was 6s, which could potentially come from various other sources (slow network, ...). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Legit. I was just looking for something that would fail if these changes accidentally reverted or changed somehow. Maybe as a unit test for untar-stream when it's moved and for fs? |
||
|
||
test('properly find and save build artifacts', () => | ||
runInstall({}, 'artifacts-finds-and-saves', async config => { | ||
const integrity = await fs.readJson(path.join(config.cwd, 'node_modules', constants.INTEGRITY_FILENAME)); | ||
|
@@ -217,7 +232,7 @@ test('changes the cache path when bumping the cache version', () => | |
const reporter = new reporters.JSONReporter({stdout: inOut}); | ||
|
||
await cache(config, reporter, {}, ['dir']); | ||
expect((JSON.parse(String(inOut.read())): any).data).toMatch(/[\\\/]v1[\\\/]?$/); | ||
expect((JSON.parse(String(inOut.read())): any).data).toMatch(/[\\\/]v(?!42[\\\/]?$)[0-9]+[\\\/]?$/); | ||
|
||
await mockConstants(config, {CACHE_VERSION: 42}, async config => { | ||
await cache(config, reporter, {}, ['dir']); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"dependencies": { | ||
"pkg": "file:./pkg-a.tgz" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,12 +93,48 @@ export default class TarballFetcher extends BaseFetcher { | |
} { | ||
const integrityInfo = this._supportedIntegrity(); | ||
|
||
const now = new Date(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this closer to where it is used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's here because I don't want each file of a single package to have a different timestamp (like it would be the case if the |
||
|
||
const fs = require('fs'); | ||
const patchedFs = Object.assign({}, fs, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, why are we patching fs where we already have our own fs functions module? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it's not the same. The Ideally the fix should be done upstream, but this issue is problematic enough that it warrants an hotfix to land faster. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to monkey patch There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, utimes is used within a closure. I also prefer to patch a function with clearly defined (if a bit buggy) inputs/output rather than a random function from a module that can change without notice. Also note that this code doesn't modify the global fs implementation. It simply creates a new, local, fs instance and pass it to tar-fs using its custom filesystem option, which seems perfect for this use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this is a good solution in regards to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm maybe it could be moved in |
||
utimes: (path, atime, mtime, cb) => { | ||
fs.stat(path, (err, stat) => { | ||
if (err) { | ||
cb(err); | ||
return; | ||
} | ||
if (stat.isDirectory()) { | ||
fs.utimes(path, atime, mtime, cb); | ||
return; | ||
} | ||
fs.open(path, 'a', (err, fd) => { | ||
if (err) { | ||
cb(err); | ||
return; | ||
} | ||
fs.futimes(fd, atime, mtime, err => { | ||
if (err) { | ||
fs.close(fd, () => cb(err)); | ||
} else { | ||
fs.close(fd, err => cb(err)); | ||
} | ||
}); | ||
}); | ||
}); | ||
}, | ||
}); | ||
|
||
const validateStream = new ssri.integrityStream(integrityInfo); | ||
const untarStream = tarFs.extract(this.dest, { | ||
strip: 1, | ||
dmode: 0o755, // all dirs should be readable | ||
fmode: 0o644, // all files should be readable | ||
chown: false, // don't chown. just leave as it is | ||
map: header => { | ||
header.mtime = now; | ||
return header; | ||
}, | ||
fs: patchedFs, | ||
}); | ||
const extractorStream = gunzip(); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any chance we can do this manually somehow rather than prolonging the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid not - even with the subsecond fs patch, subsecond timings still won't work on filesystems that don't support it at all (I think it might be the case on CircleCI), so we need to wait at least >1s to be sure that the mtime will change.
Another option might be to mock the fs module here, but it seemed a bit hazardous so I went with the sleep (since the tests are executed in parallel, it doesn't actually take the whole two seconds in practice).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm not following (or maybe this doesn't work, I haven't tested it) but won't this do the trick?
https://nodejs.org/api/fs.html#fs_fs_futimes_fd_atime_mtime_callback