🐛 Fix race condition in file download

This commit is contained in:
C-3PO 2018-07-08 19:09:31 +02:00
parent 662c2d5f43
commit 8b1ad6f7e2
Signed by: c3po
GPG key ID: 62993C4BB4D86F24
3 changed files with 11 additions and 13 deletions

View file

@ -15,9 +15,11 @@ export default function saveResponse(
//Remember file size //Remember file size
const headerLength = Number(response.headers['content-length']); const headerLength = Number(response.headers['content-length']);
const writeStream = fs.createWriteStream(filePath);
//If we receive a part of the response, write it to disk //If we receive a part of the response, write it to disk
let previousChunk: Promise<void> = new Promise((innerResolve) => { innerResolve(); });
let totalLength = 0; let totalLength = 0;
const chunkPromises: Array<Promise<void>> = [];
response.on('data', (chunk: Buffer) => { response.on('data', (chunk: Buffer) => {
totalLength += chunk.length; totalLength += chunk.length;
@ -26,18 +28,12 @@ export default function saveResponse(
return reject(`Expected length ${headerLength} but received at least ${totalLength}.`); return reject(`Expected length ${headerLength} but received at least ${totalLength}.`);
} }
//If previous chunk was not yet written to disk, wait until it finished to avoid a race condition
previousChunk.then(() => {
previousChunk = new Promise((innerResolve, innerReject) => {
//Write chunk to disk //Write chunk to disk
fs.appendFile(filePath, chunk, { encoding: 'binary' }, (error) => { chunkPromises.push(new Promise((writeResolve) => {
if (error) { writeStream.write(chunk, () => {
return reject(`Could not write to disk: [${error.code}] ${error.name}: ${error.message}.`); writeResolve();
}
innerResolve();
});
});
}); });
}));
}); });
//If we finished reading response, check for correctness, then return it //If we finished reading response, check for correctness, then return it
@ -50,7 +46,7 @@ export default function saveResponse(
//wait until everything is written to disk, then return file name //wait until everything is written to disk, then return file name
//TODO: need to automatically delete file once it is no longer used //TODO: need to automatically delete file once it is no longer used
//TODO: need to provide methods to seek through file //TODO: need to provide methods to seek through file
previousChunk.then(() => { Promise.all(chunkPromises).then(() => {
resolve(filePath); resolve(filePath);
}); });
}); });

View file

@ -24,6 +24,7 @@ export default function extractFileStream(file: ISsnFileEntry, inputStream: stre
//pipe into decompression //pipe into decompression
const decompressTransform = zlib.createInflateRaw(); const decompressTransform = zlib.createInflateRaw();
decompressTransform.on('error', (error) => { decompressTransform.on('error', (error) => {
//TODO: need to throw error sync, not async
throw new Error(`Error during decompression: ${error.message}`); throw new Error(`Error during decompression: ${error.message}`);
}); });
curStream = curStream.pipe(decompressTransform); curStream = curStream.pipe(decompressTransform);

View file

@ -32,6 +32,7 @@ export default async function getPatch(product: Product, from: number, to: numbe
//Then we need to wait for disks to finish download before we can extract individual files //Then we need to wait for disks to finish download before we can extract individual files
//TODO: we can optimize this to already extract some files as soon as their relevant parts are downloaded //TODO: we can optimize this to already extract some files as soon as their relevant parts are downloaded
const diskFilenames = await Promise.all(diskFiles); const diskFilenames = await Promise.all(diskFiles);
console.debug(diskFilenames);
//const dvArray = bufferArray.map((buffer) => new DataView(buffer)); //const dvArray = bufferArray.map((buffer) => new DataView(buffer));
//TODO: Verify that downloaded files match the hash in `solidpkg.pieces` //TODO: Verify that downloaded files match the hash in `solidpkg.pieces`