Skip to content

Commit

Permalink
fix: handle parallel installs
Browse files Browse the repository at this point in the history
  • Loading branch information
merceyz committed Jan 23, 2022
1 parent f17384e commit 26b41b0
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 39 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
env:
TARGET_BRANCH: ${{github.event.pull_request.base.ref}}

- name: 'Check for type errors'
run: yarn typecheck

build:
strategy:
fail-fast: false
Expand Down
45 changes: 40 additions & 5 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion mkshims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async function main() {
const remapPath = (p: string) => path.resolve(__dirname, path.relative(virtualNodewinDir, p));

const easyStatFs = Object.assign(Object.create(fs), {
readFile: (p: string, encoding: string, cb: (err: any, str: string) => void) => fs.readFile(remapPath(p), encoding, cb),
readFile: (p: string, encoding: BufferEncoding, cb: (err: any, str: string) => void) => fs.readFile(remapPath(p), encoding, cb),
stat: (p: string, cb: () => void) => fs.stat(remapPath(p), cb),
});

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"@babel/preset-typescript": "^7.13.0",
"@types/debug": "^4.1.5",
"@types/jest": "^26.0.23",
"@types/node": "^13.9.2",
"@types/node": "^17.0.10",
"@types/rimraf": "^3.0.2",
"@types/semver": "^7.1.0",
"@types/tar": "^4.0.3",
"@types/which": "^1.3.2",
Expand All @@ -43,6 +44,7 @@
"eslint-plugin-arca": "^0.9.5",
"jest": "^26.0.0",
"nock": "^13.0.4",
"rimraf": "^3.0.2",
"semver": "^7.1.3",
"supports-color": "^7.1.0",
"tar": "^6.0.1",
Expand All @@ -60,6 +62,7 @@
"corepack": "ts-node ./sources/main.ts",
"prepack": "node ./.yarn/releases/*.*js build",
"postpack": "rm -rf dist shims",
"typecheck": "tsc --noEmit",
"test": "yarn jest"
},
"files": [
Expand Down
59 changes: 35 additions & 24 deletions sources/corepackUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,39 +87,50 @@ export async function installVersion(installTarget: string, locator: Locator, {s
const url = spec.url.replace(`{}`, locator.reference);
debugUtils.log(`Installing ${locator.name}@${locator.reference} from ${url}`);

return await fsUtils.mutex(installFolder, async () => {
// Creating a temporary folder inside the install folder means that we
// are sure it'll be in the same drive as the destination, so we can
// just move it there atomically once we are done
// Creating a temporary folder inside the install folder means that we
// are sure it'll be in the same drive as the destination, so we can
// just move it there atomically once we are done

const tmpFolder = folderUtils.getTemporaryFolder(installTarget);
const stream = await httpUtils.fetchUrlStream(url);
const tmpFolder = folderUtils.getTemporaryFolder(installTarget);
const stream = await httpUtils.fetchUrlStream(url);

const parsedUrl = new URL(url);
const ext = path.posix.extname(parsedUrl.pathname);
const parsedUrl = new URL(url);
const ext = path.posix.extname(parsedUrl.pathname);

let outputFile: string | null = null;
let outputFile: string | null = null;

let sendTo: any;
if (ext === `.tgz`) {
sendTo = tar.x({strip: 1, cwd: tmpFolder});
} else if (ext === `.js`) {
outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname));
sendTo = fs.createWriteStream(outputFile);
}
let sendTo: any;
if (ext === `.tgz`) {
sendTo = tar.x({strip: 1, cwd: tmpFolder});
} else if (ext === `.js`) {
outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname));
sendTo = fs.createWriteStream(outputFile);
}

stream.pipe(sendTo);
stream.pipe(sendTo);

await new Promise(resolve => {
sendTo.on(`finish`, resolve);
});
await new Promise(resolve => {
sendTo.on(`finish`, resolve);
});

await fs.promises.mkdir(path.dirname(installFolder), {recursive: true});
await fs.promises.mkdir(path.dirname(installFolder), {recursive: true});
try {
await fs.promises.rename(tmpFolder, installFolder);
} catch (err) {
if (
err.code === `ENOTEMPTY` ||
// On Windows the error code is EPERM so we check if it is a directory
(err.code === `EPERM` && (await fs.promises.stat(installFolder)).isDirectory())
) {
debugUtils.log(`Another instance of corepack installed ${locator.name}@${locator.reference}`);
await fsUtils.rimraf(tmpFolder);
} else {
throw err;
}
}

debugUtils.log(`Install finished`);
return installFolder;
});
debugUtils.log(`Install finished`);
return installFolder;
}

export async function runVersion(installSpec: { location: string, spec: PackageManagerSpec }, locator: Locator, binName: string, args: Array<string>, context: Context) {
Expand Down
25 changes: 23 additions & 2 deletions sources/fsUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
export async function mutex<T>(p: string, cb: () => Promise<T>) {
return await cb();
import fs from 'fs';

export async function rimraf(path: string) {
const [major, minor] = process.versions.node.split(`.`).map(section => Number(section));

if (major > 14 || (major === 14 && minor >= 14)) {
// rm was added in v14.14.0
return fs.promises.rm(path, {recursive: true});
} else if (major > 12 || (major === 12 && minor >= 10)) {
// rmdir got support for recursive in v12.10.0 and was deprecated in v14.14.0
return fs.promises.rmdir(path, {recursive: true});
} else {
const rimraf = await import(`rimraf`);
return new Promise<void>((resolve, reject) => {
rimraf.default(path, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
27 changes: 27 additions & 0 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,30 @@ it(`should support running package managers with bin array`, async () => {
});
});
});

it(`should handle parallel installs`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `[email protected]`,
});

await expect(Promise.all([
runCli(cwd, [`yarn`, `--version`]),
runCli(cwd, [`yarn`, `--version`]),
runCli(cwd, [`yarn`, `--version`]),
])).resolves.toMatchObject([
{
stdout: `2.2.2\n`,
exitCode: 0,
},
{
stdout: `2.2.2\n`,
exitCode: 0,
},
{
stdout: `2.2.2\n`,
exitCode: 0,
},
]);
});
});
Binary file added tests/nock/5ed9c2e2a56a83b54950e0282b2c406c.dat
Binary file not shown.
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lib": ["dom", "es2017", "esnext.asynciterable"],
"module": "commonjs",
"resolveJsonModule": true,
"skipLibCheck": true,
"target": "es2017"
}
}
41 changes: 35 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,16 @@ __metadata:
languageName: node
linkType: hard

"@types/glob@npm:*":
version: 7.2.0
resolution: "@types/glob@npm:7.2.0"
dependencies:
"@types/minimatch": "*"
"@types/node": "*"
checksum: 6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19
languageName: node
linkType: hard

"@types/graceful-fs@npm:^4.1.2":
version: 4.1.5
resolution: "@types/graceful-fs@npm:4.1.5"
Expand Down Expand Up @@ -1123,6 +1133,13 @@ __metadata:
languageName: node
linkType: hard

"@types/minimatch@npm:*":
version: 3.0.5
resolution: "@types/minimatch@npm:3.0.5"
checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92
languageName: node
linkType: hard

"@types/minipass@npm:*":
version: 2.2.0
resolution: "@types/minipass@npm:2.2.0"
Expand All @@ -1139,10 +1156,10 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:^13.9.2":
version: 13.9.2
resolution: "@types/node@npm:13.9.2"
checksum: 58e82c03d3e51981520119ec5fd104c43762d180106a3ebc7c2c28f1d567b5f505d3d527acc22e7dae8ca9fdaaeb562d3cd77f801f55425eb664afb73c63039b
"@types/node@npm:^17.0.10":
version: 17.0.10
resolution: "@types/node@npm:17.0.10"
checksum: 979e83d642a2b4f18fa1a4233f884822c05abc7acd0836024aa964187f8446432b21f7913e72fe2b3927c4a811c27a0b6cd60ac7c4ac6a6762cfbab70782aa6a
languageName: node
linkType: hard

Expand All @@ -1160,6 +1177,16 @@ __metadata:
languageName: node
linkType: hard

"@types/rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "@types/rimraf@npm:3.0.2"
dependencies:
"@types/glob": "*"
"@types/node": "*"
checksum: b47fa302f46434cba704d20465861ad250df79467d3d289f9d6490d3aeeb41e8cb32dd80bd1a8fd833d1e185ac719fbf9be12e05ad9ce9be094d8ee8f1405347
languageName: node
linkType: hard

"@types/semver@npm:^7.1.0":
version: 7.1.0
resolution: "@types/semver@npm:7.1.0"
Expand Down Expand Up @@ -2365,7 +2392,8 @@ __metadata:
"@babel/preset-typescript": ^7.13.0
"@types/debug": ^4.1.5
"@types/jest": ^26.0.23
"@types/node": ^13.9.2
"@types/node": ^17.0.10
"@types/rimraf": ^3.0.2
"@types/semver": ^7.1.0
"@types/tar": ^4.0.3
"@types/which": ^1.3.2
Expand All @@ -2381,6 +2409,7 @@ __metadata:
eslint-plugin-arca: ^0.9.5
jest: ^26.0.0
nock: ^13.0.4
rimraf: ^3.0.2
semver: ^7.1.3
supports-color: ^7.1.0
tar: ^6.0.1
Expand Down Expand Up @@ -6116,7 +6145,7 @@ resolve@^1.3.2:
languageName: node
linkType: hard

"rimraf@npm:^3.0.0":
"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
dependencies:
Expand Down

0 comments on commit 26b41b0

Please sign in to comment.