Use lockfile to prevent inter-process races

See #86
This commit is contained in:
Michael Jackson
2018-05-22 22:06:55 -04:00
parent 5b1750c182
commit 0f895bf04e
4 changed files with 73 additions and 42 deletions

View File

@ -1,11 +1,7 @@
require("isomorphic-fetch");
const fs = require("fs");
const mkdirp = require("mkdirp");
const gunzip = require("gunzip-maybe");
const tar = require("tar-fs");
const createTempPath = require("./createTempPath");
function stripNamePrefix(headers) {
// Most packages have header names that look like "package/index.js"
// so we shorten that to just "index.js" here. A few packages use a
@ -35,38 +31,10 @@ function extractResponse(response, outputDir) {
});
}
function fetchPackage(packageConfig) {
return new Promise((resolve, reject) => {
const tarballURL = packageConfig.dist.tarball;
const outputDir = createTempPath(packageConfig.name, packageConfig.version);
function fetchPackage(tarballURL, outputDir) {
console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`);
console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`);
fs.access(outputDir, error => {
if (error) {
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
// ENOENT or ENOTDIR are to be expected when we haven't yet
// fetched a package for the first time. Carry on!
mkdirp(outputDir, error => {
if (error) {
reject(error);
} else {
resolve(
fetch(tarballURL)
.then(res => extractResponse(res, outputDir))
.then(() => outputDir)
);
}
});
} else {
reject(error);
}
} else {
// Best case: we already have this package cached on disk!
resolve(outputDir);
}
});
});
return fetch(tarballURL).then(res => extractResponse(res, outputDir));
}
module.exports = fetchPackage;

View File

@ -1,15 +1,66 @@
const fs = require("fs");
const mkdirp = require("mkdirp");
const lockfile = require("proper-lockfile");
const createMutex = require("./createMutex");
const createTempPath = require("./createTempPath");
const fetchPackage = require("./fetchPackage");
const fetchMutex = createMutex((packageConfig, callback) => {
fetchPackage(packageConfig).then(
outputDir => {
callback(null, outputDir);
},
error => {
callback(error);
const tarballURL = packageConfig.dist.tarball;
const outputDir = createTempPath(packageConfig.name, packageConfig.version);
fs.access(outputDir, error => {
if (error) {
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
// ENOENT or ENOTDIR are to be expected when we haven't yet
// fetched a package for the first time. Carry on!
mkdirp.sync(outputDir);
const release = lockfile.lockSync(outputDir);
fetchPackage(tarballURL, outputDir).then(
() => {
release();
callback(null, outputDir);
},
error => {
release();
callback(error);
}
);
} else {
callback(error);
}
} else {
lockfile.check(outputDir).then(isLocked => {
if (isLocked) {
// Another process on this same machine has locked the
// directory. We need to wait for it to be unlocked
// before we callback.
const timer = setInterval(() => {
lockfile.check(outputDir).then(
isLocked => {
if (!isLocked) {
clearInterval(timer);
callback(null, outputDir);
}
},
error => {
clearInterval(timer);
callback(error);
}
);
}, 10);
timer.unref();
} else {
// Best case: we already have this package cached on disk
// and it's not locked!
callback(null, outputDir);
}
}, callback);
}
);
});
}, packageConfig => packageConfig.dist.tarball);
function getPackage(packageConfig) {