Skip to content

Commit acf64a7

Browse files
authored
fix: cache local environment values (#120)
Yes this is a bit dirty because the report is now generated once at the beginning of a command which could be considered a side effect (but normally treeshaking will only include it only if needed), but the whole process.report is a weirdly built API meant for debugging and not really meant to be used in a normal program But somehow when the call is done in the beginning of the process, this call is very fast, but when it's called after having run a http request (as is currently the case, a single call takes a lot more time to complete, it takes 40s or more on my system when called at the end of npm upgrade) but only a few milliseconds when called at the beginning (this is why it's best to run it outside the function at the beginning of the process as a side effect instead of calling getReport on demand and cache the result) Here is a log of console.time('report') and console.timeEnd('report') before and after the getReport call when run in the end of the upgrade command: ⠼report: 3:10.573 (m:ss.mmm) when run in the beginning of the process at the top level report: 1.943ms This fixes npm hanging npm/cli#4028, npm/cli#7814, npm/cli#7868
1 parent 4751cb4 commit acf64a7

File tree

2 files changed

+114
-33
lines changed

2 files changed

+114
-33
lines changed

lib/current-env.js

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const process = require('node:process')
22
const nodeOs = require('node:os')
3+
const fs = require('node:fs')
34

45
function isMusl (file) {
56
return file.includes('libc.musl-') || file.includes('ld-musl-')
@@ -13,12 +14,23 @@ function cpu () {
1314
return process.arch
1415
}
1516

16-
function libc (osName) {
17-
// this is to make it faster on non linux machines
18-
if (osName !== 'linux') {
17+
const LDD_PATH = '/usr/bin/ldd'
18+
function getFamilyFromFilesystem () {
19+
try {
20+
const content = fs.readFileSync(LDD_PATH, 'utf-8')
21+
if (content.includes('musl')) {
22+
return 'musl'
23+
}
24+
if (content.includes('GNU C Library')) {
25+
return 'glibc'
26+
}
27+
return null
28+
} catch {
1929
return undefined
2030
}
21-
let family
31+
}
32+
33+
function getFamilyFromReport () {
2234
const originalExclude = process.report.excludeNetwork
2335
process.report.excludeNetwork = true
2436
const report = process.report.getReport()
@@ -27,6 +39,22 @@ function libc (osName) {
2739
family = 'glibc'
2840
} else if (Array.isArray(report.sharedObjects) && report.sharedObjects.some(isMusl)) {
2941
family = 'musl'
42+
} else {
43+
family = null
44+
}
45+
return family
46+
}
47+
48+
let family
49+
function libc (osName) {
50+
if (osName !== 'linux') {
51+
return undefined
52+
}
53+
if (family === undefined) {
54+
family = getFamilyFromFilesystem()
55+
if (family === undefined) {
56+
family = getFamilyFromReport()
57+
}
3058
}
3159
return family
3260
}

test/check-platform.js

+82-29
Original file line numberDiff line numberDiff line change
@@ -94,48 +94,98 @@ t.test('wrong libc with overridden libc', async t =>
9494
}), { code: 'EBADPLATFORM' }))
9595

9696
t.test('libc', (t) => {
97-
let PLATFORM = ''
98-
99-
const _processPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
100-
Object.defineProperty(process, 'platform', {
101-
enumerable: true,
102-
configurable: true,
103-
get: () => PLATFORM,
104-
})
105-
97+
let noCacheChckPtfm
98+
let PLATFORM = 'linux'
10699
let REPORT = {}
107-
const _processReport = process.report.getReport
108-
process.report.getReport = () => REPORT
109-
110-
t.teardown(() => {
111-
Object.defineProperty(process, 'platform', _processPlatform)
112-
process.report.getReport = _processReport
113-
})
100+
let readFileSync
101+
let noCache = true
102+
103+
function withCache (cb) {
104+
noCache = false
105+
cb()
106+
noCache = true
107+
withoutLibcCache()
108+
}
109+
110+
function withoutLibcCache () {
111+
readFileSync = () => {
112+
throw new Error('File not found')
113+
}
114+
const original = t.mock('..', {
115+
'../lib/current-env': t.mock('../lib/current-env', {
116+
'node:fs': {
117+
readFileSync: () => {
118+
return readFileSync()
119+
},
120+
},
121+
'node:process': Object.defineProperty({
122+
report: {
123+
getReport: () => REPORT,
124+
},
125+
}, 'platform', {
126+
enumerable: true,
127+
get: () => PLATFORM,
128+
}),
129+
}),
130+
}).checkPlatform
131+
noCacheChckPtfm = (...args) => {
132+
try {
133+
original(...args)
134+
} finally {
135+
if (noCache) {
136+
withoutLibcCache()
137+
}
138+
}
139+
}
140+
}
141+
142+
withoutLibcCache()
114143

115144
t.test('fails when not in linux', (t) => {
116145
PLATFORM = 'darwin'
117146

118-
t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
147+
t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
119148
'fails for glibc when not in linux')
120-
t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' },
149+
t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' },
121150
'fails for musl when not in linux')
122151
t.end()
123152
})
124153

125154
t.test('glibc', (t) => {
126155
PLATFORM = 'linux'
127156

157+
withCache(() => {
158+
readFileSync = () => 'this ldd file contains GNU C Library'
159+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }),
160+
'allows glibc on glibc from ldd file')
161+
162+
readFileSync = () => {
163+
throw new Error('File not found')
164+
}
165+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc from ldd file cache')
166+
})
167+
128168
REPORT = {}
129-
t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
169+
t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
130170
'fails when report is missing header property')
131171

132172
REPORT = { header: {} }
133-
t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
173+
t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
134174
'fails when header is missing glibcVersionRuntime property')
135175

136-
REPORT = { header: { glibcVersionRuntime: '1' } }
137-
t.doesNotThrow(() => checkPlatform({ libc: 'glibc' }), 'allows glibc on glibc')
138-
t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' },
176+
withCache(() => {
177+
REPORT = { header: { glibcVersionRuntime: '1' } }
178+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc on glibc')
179+
180+
REPORT = {}
181+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc from report cache')
182+
})
183+
184+
readFileSync = () => 'this ldd file is unsupported'
185+
t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
186+
'fails when ldd file exists but is not something known')
187+
188+
t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' },
139189
'does not allow musl on glibc')
140190

141191
t.end()
@@ -144,25 +194,28 @@ t.test('libc', (t) => {
144194
t.test('musl', (t) => {
145195
PLATFORM = 'linux'
146196

197+
readFileSync = () => 'this ldd file contains musl'
198+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl from ldd file')
199+
147200
REPORT = {}
148-
t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' },
201+
t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' },
149202
'fails when report is missing sharedObjects property')
150203

151204
REPORT = { sharedObjects: {} }
152-
t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' },
205+
t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' },
153206
'fails when sharedObjects property is not an array')
154207

155208
REPORT = { sharedObjects: [] }
156-
t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' },
209+
t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' },
157210
'fails when sharedObjects does not contain musl')
158211

159212
REPORT = { sharedObjects: ['ld-musl-foo'] }
160-
t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as ld-musl-')
213+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl as ld-musl-')
161214

162215
REPORT = { sharedObjects: ['libc.musl-'] }
163-
t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as libc.musl-')
216+
t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl as libc.musl-')
164217

165-
t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
218+
t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },
166219
'does not allow glibc on musl')
167220

168221
t.end()

0 commit comments

Comments
 (0)