Skip to content

Commit 8ee9a3d

Browse files
goloveychukarcanis
andauthored
chore: close fd in js impl (#6760)
## What's the problem this PR addresses? - We forgot the close the file descriptor when releasing JS files. This causes EMFILE on some jobs. - The cache was mistakenly removed from the libzip backend, causing install times to jump, especially on pnpm installs. Fixes #6758 ## Checklist <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed. --------- Co-authored-by: Maël Nison <[email protected]>
1 parent 5bea831 commit 8ee9a3d

File tree

6 files changed

+110
-34
lines changed

6 files changed

+110
-34
lines changed

.pnp.cjs

+34-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.yarn/versions/baa73f5a.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
releases:
2+
"@yarnpkg/cli": patch
3+
"@yarnpkg/libzip": patch
4+
"@yarnpkg/pnp": patch
5+
6+
declined:
7+
- "@yarnpkg/plugin-compat"
8+
- "@yarnpkg/plugin-constraints"
9+
- "@yarnpkg/plugin-dlx"
10+
- "@yarnpkg/plugin-essentials"
11+
- "@yarnpkg/plugin-file"
12+
- "@yarnpkg/plugin-init"
13+
- "@yarnpkg/plugin-interactive-tools"
14+
- "@yarnpkg/plugin-nm"
15+
- "@yarnpkg/plugin-npm-cli"
16+
- "@yarnpkg/plugin-pack"
17+
- "@yarnpkg/plugin-patch"
18+
- "@yarnpkg/plugin-pnp"
19+
- "@yarnpkg/plugin-pnpm"
20+
- "@yarnpkg/plugin-stage"
21+
- "@yarnpkg/plugin-typescript"
22+
- "@yarnpkg/plugin-version"
23+
- "@yarnpkg/plugin-workspace-tools"
24+
- vscode-zipfs
25+
- "@yarnpkg/builder"
26+
- "@yarnpkg/core"
27+
- "@yarnpkg/doctor"
28+
- "@yarnpkg/fslib"
29+
- "@yarnpkg/nm"
30+
- "@yarnpkg/pnpify"
31+
- "@yarnpkg/sdks"

packages/yarnpkg-libzip/sources/ZipFS.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export interface Stat {
7676
}
7777

7878
export interface ZipImpl {
79+
filesShouldBeCached: boolean;
7980
deleteEntry(index: number): void;
8081
getFileSource(index: number): {data: Buffer, compressionMethod: number};
8182
setFileSource(target: PortablePath, compression: CompressionData, buffer: Buffer): number;
@@ -109,6 +110,12 @@ export class ZipFS extends BasePortableFakeFS {
109110
private readonly listings: Map<PortablePath, Set<Filename>> = new Map();
110111
private readonly entries: Map<PortablePath, number> = new Map();
111112

113+
/**
114+
* A cache of indices mapped to file sources.
115+
* Populated by `setFileSource` calls.
116+
* Required for supporting read after write.
117+
*/
118+
private readonly fileSources: Map<number, Buffer> = new Map();
112119

113120
private symlinkCount: number;
114121

@@ -736,6 +743,8 @@ export class ZipFS extends BasePortableFakeFS {
736743
if (typeof entry === `undefined`)
737744
return;
738745

746+
this.fileSources.delete(entry);
747+
739748
if (this.isSymbolicLink(entry)) {
740749
this.symlinkCount--;
741750
}
@@ -819,6 +828,7 @@ export class ZipFS extends BasePortableFakeFS {
819828
}
820829

821830
const newIndex = this.zipImpl.setFileSource(target, compression, buffer);
831+
this.fileSources.set(newIndex, buffer);
822832

823833
return newIndex;
824834
}
@@ -842,8 +852,15 @@ export class ZipFS extends BasePortableFakeFS {
842852
private getFileSource(index: number, opts: {asyncDecompress: true}): Promise<Buffer>;
843853
private getFileSource(index: number, opts: {asyncDecompress: boolean}): Promise<Buffer> | Buffer;
844854
private getFileSource(index: number, opts: {asyncDecompress: boolean} = {asyncDecompress: false}): Promise<Buffer> | Buffer {
855+
const cachedFileSource = this.fileSources.get(index);
856+
if (typeof cachedFileSource !== `undefined`)
857+
return cachedFileSource;
858+
845859
const {data, compressionMethod} = this.zipImpl.getFileSource(index);
846860
if (compressionMethod === STORE) {
861+
if (this.zipImpl.filesShouldBeCached)
862+
this.fileSources.set(index, data);
863+
847864
return data;
848865
} else if (compressionMethod === DEFLATE) {
849866
if (opts.asyncDecompress) {
@@ -852,12 +869,17 @@ export class ZipFS extends BasePortableFakeFS {
852869
if (error) {
853870
reject(error);
854871
} else {
872+
if (this.zipImpl.filesShouldBeCached)
873+
this.fileSources.set(index, result);
855874
resolve(result);
856875
}
857876
});
858877
});
859878
} else {
860-
return zlib.inflateRawSync(data);
879+
const decompressedData = zlib.inflateRawSync(data);
880+
if (this.zipImpl.filesShouldBeCached)
881+
this.fileSources.set(index, decompressedData);
882+
return decompressedData;
861883
}
862884
} else {
863885
throw new Error(`Unsupported compression method: ${compressionMethod}`);

packages/yarnpkg-libzip/sources/jsZipImpl.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ export interface Entry {
2323
}
2424

2525
export class JsZipImpl implements ZipImpl {
26-
fd: number;
27-
baseFs: FakeFS<PortablePath>;
28-
entries: Array<Entry>;
26+
private fd: number | `closed`;
27+
private baseFs: FakeFS<PortablePath>;
28+
private entries: Array<Entry>;
29+
public filesShouldBeCached = false;
2930

3031
constructor(opts: ZipImplInput) {
3132
if (`buffer` in opts)
@@ -37,7 +38,13 @@ export class JsZipImpl implements ZipImpl {
3738
this.baseFs = opts.baseFs;
3839
this.fd = this.baseFs.openSync(opts.path, `r`);
3940

40-
this.entries = JsZipImpl.readZipSync(this.fd, this.baseFs, opts.size);
41+
try {
42+
this.entries = JsZipImpl.readZipSync(this.fd, this.baseFs, opts.size);
43+
} catch (error) {
44+
this.baseFs.closeSync(this.fd);
45+
this.fd = `closed`;
46+
throw error;
47+
}
4148
}
4249

4350
static readZipSync(fd: number, baseFs: FakeFS<PortablePath>, fileSize: number): Array<Entry> {
@@ -213,7 +220,10 @@ export class JsZipImpl implements ZipImpl {
213220
return -1;
214221
}
215222

216-
getFileSource(index: number): {data: Buffer, compressionMethod: number} {
223+
getFileSource(index: number) {
224+
if (this.fd === `closed`)
225+
throw new Error(`ZIP file is closed`);
226+
217227
const entry = this.entries[index];
218228
const localHeaderBuf = Buffer.alloc(30);
219229

@@ -237,6 +247,10 @@ export class JsZipImpl implements ZipImpl {
237247
}
238248

239249
discard(): void {
250+
if (this.fd !== `closed`) {
251+
this.baseFs.closeSync(this.fd);
252+
this.fd = `closed`;
253+
}
240254
}
241255

242256
addDirectory(path: string): number {

packages/yarnpkg-libzip/sources/libzipImpl.ts

+2-14
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,11 @@ export class LibZipImpl implements ZipImpl {
2020
private readonly libzip: Libzip;
2121
private readonly lzSource: number;
2222
private readonly zip: number;
23-
24-
/**
25-
* A cache of indices mapped to file sources.
26-
* Populated by `setFileSource` calls.
27-
* Required for supporting read after write.
28-
*/
29-
private readonly fileSources: Map<number, Buffer> = new Map();
30-
3123
private readonly listings: Array<string>;
3224
private readonly symlinkCount: number;
3325

26+
public filesShouldBeCached = true;
27+
3428
constructor(opts: ZipImplInput) {
3529
const buffer = `buffer` in opts
3630
? opts.buffer
@@ -127,7 +121,6 @@ export class LibZipImpl implements ZipImpl {
127121
throw this.makeLibzipError(this.libzip.getError(this.zip));
128122
}
129123
}
130-
this.fileSources.set(newIndex, buffer);
131124
return newIndex;
132125
} catch (error) {
133126
this.libzip.source.free(lzSource);
@@ -164,10 +157,6 @@ export class LibZipImpl implements ZipImpl {
164157
}
165158

166159
getFileSource(index: number) {
167-
const cachedFileSource = this.fileSources.get(index);
168-
if (typeof cachedFileSource !== `undefined`)
169-
return {data: cachedFileSource, compressionMethod: 0};
170-
171160
const stat = this.libzip.struct.statS();
172161

173162
const rc = this.libzip.statIndex(this.zip, index, 0, 0, stat);
@@ -206,7 +195,6 @@ export class LibZipImpl implements ZipImpl {
206195
}
207196

208197
deleteEntry(index: number) {
209-
this.fileSources.delete(index);
210198
const rc = this.libzip.delete(this.zip, index);
211199
if (rc === -1) {
212200
throw this.makeLibzipError(this.libzip.getError(this.zip));

packages/yarnpkg-pnp/sources/hook.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)