From 8b1ad6f7e24c2a4af6f56d1ea69f90f04afa4d0b Mon Sep 17 00:00:00 2001 From: C-3PO Date: Sun, 8 Jul 2018 19:09:31 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20race=20condition=20in=20fi?= =?UTF-8?q?le=20download?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cdn/funcs/saveResponse.ts | 22 +++++++++------------- src/ssn/extractFileStream.ts | 1 + src/ssn/getPatch.ts | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/cdn/funcs/saveResponse.ts b/src/cdn/funcs/saveResponse.ts index 94e8ef8..f3f1ab5 100644 --- a/src/cdn/funcs/saveResponse.ts +++ b/src/cdn/funcs/saveResponse.ts @@ -15,9 +15,11 @@ export default function saveResponse( //Remember file size const headerLength = Number(response.headers['content-length']); + const writeStream = fs.createWriteStream(filePath); + //If we receive a part of the response, write it to disk - let previousChunk: Promise = new Promise((innerResolve) => { innerResolve(); }); let totalLength = 0; + const chunkPromises: Array> = []; response.on('data', (chunk: Buffer) => { totalLength += chunk.length; @@ -26,18 +28,12 @@ export default function saveResponse( 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 - fs.appendFile(filePath, chunk, { encoding: 'binary' }, (error) => { - if (error) { - return reject(`Could not write to disk: [${error.code}] ${error.name}: ${error.message}.`); - } - innerResolve(); - }); + //Write chunk to disk + chunkPromises.push(new Promise((writeResolve) => { + writeStream.write(chunk, () => { + writeResolve(); }); - }); + })); }); //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 //TODO: need to automatically delete file once it is no longer used //TODO: need to provide methods to seek through file - previousChunk.then(() => { + Promise.all(chunkPromises).then(() => { resolve(filePath); }); }); diff --git a/src/ssn/extractFileStream.ts b/src/ssn/extractFileStream.ts index a1eb4de..169cec0 100644 --- a/src/ssn/extractFileStream.ts +++ b/src/ssn/extractFileStream.ts @@ -24,6 +24,7 @@ export default function extractFileStream(file: ISsnFileEntry, inputStream: stre //pipe into decompression const decompressTransform = zlib.createInflateRaw(); decompressTransform.on('error', (error) => { + //TODO: need to throw error sync, not async throw new Error(`Error during decompression: ${error.message}`); }); curStream = curStream.pipe(decompressTransform); diff --git a/src/ssn/getPatch.ts b/src/ssn/getPatch.ts index e006503..fa64308 100644 --- a/src/ssn/getPatch.ts +++ b/src/ssn/getPatch.ts @@ -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 //TODO: we can optimize this to already extract some files as soon as their relevant parts are downloaded const diskFilenames = await Promise.all(diskFiles); + console.debug(diskFilenames); //const dvArray = bufferArray.map((buffer) => new DataView(buffer)); //TODO: Verify that downloaded files match the hash in `solidpkg.pieces`