Merge pull request #5355 from overleaf/em-stack-overflow

Avoid stack overflow with synchronous jobs in ProjectZipStreamManager

GitOrigin-RevId: b7e948b98c675527a8417247a840ab27690c1027
This commit is contained in:
Eric Mc Sween
2021-10-07 08:04:56 +00:00
committed by Copybot
parent 13b8321986
commit 94b36dfbba
@@ -1,17 +1,3 @@
/* eslint-disable
camelcase,
node/handle-callback-err,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let ProjectZipStreamManager let ProjectZipStreamManager
const archiver = require('archiver') const archiver = require('archiver')
const async = require('async') const async = require('async')
@@ -21,162 +7,124 @@ const ProjectGetter = require('../Project/ProjectGetter')
const FileStoreHandler = require('../FileStore/FileStoreHandler') const FileStoreHandler = require('../FileStore/FileStoreHandler')
module.exports = ProjectZipStreamManager = { module.exports = ProjectZipStreamManager = {
createZipStreamForMultipleProjects(project_ids, callback) { createZipStreamForMultipleProjects(projectIds, callback) {
// We'll build up a zip file that contains multiple zip files // We'll build up a zip file that contains multiple zip files
if (callback == null) {
callback = function (error, stream) {}
}
const archive = archiver('zip') const archive = archiver('zip')
archive.on('error', err => archive.on('error', err =>
logger.err( logger.err(
{ err, project_ids }, { err, projectIds },
'something went wrong building archive of project' 'something went wrong building archive of project'
) )
) )
callback(null, archive) callback(null, archive)
const jobs = [] const jobs = projectIds.map(projectId => cb => {
for (const project_id of Array.from(project_ids || [])) { ProjectGetter.getProject(projectId, { name: true }, (error, project) => {
;(project_id => if (error) {
jobs.push(callback => return cb(error)
ProjectGetter.getProject( }
project_id, logger.log(
{ name: true }, { projectId, name: project.name },
function (error, project) { 'appending project to zip stream'
if (error != null) { )
return callback(error) ProjectZipStreamManager.createZipStreamForProject(
} projectId,
logger.log( (error, stream) => {
{ project_id, name: project.name }, if (error) {
'appending project to zip stream' return cb(error)
)
return ProjectZipStreamManager.createZipStreamForProject(
project_id,
function (error, stream) {
if (error != null) {
return callback(error)
}
archive.append(stream, { name: `${project.name}.zip` })
return stream.on('end', function () {
logger.log(
{ project_id, name: project.name },
'zip stream ended'
)
return callback()
})
}
)
} }
) archive.append(stream, { name: `${project.name}.zip` })
))(project_id) stream.on('end', () => {
} logger.log({ projectId, name: project.name }, 'zip stream ended')
cb()
})
}
)
})
})
return async.series(jobs, function () { async.series(jobs, () => {
logger.log( logger.log(
{ project_ids }, { projectIds },
'finished creating zip stream of multiple projects' 'finished creating zip stream of multiple projects'
) )
return archive.finalize() archive.finalize()
}) })
}, },
createZipStreamForProject(project_id, callback) { createZipStreamForProject(projectId, callback) {
if (callback == null) {
callback = function (error, stream) {}
}
const archive = archiver('zip') const archive = archiver('zip')
// return stream immediately before we start adding things to it // return stream immediately before we start adding things to it
archive.on('error', err => archive.on('error', err =>
logger.err( logger.err(
{ err, project_id }, { err, projectId },
'something went wrong building archive of project' 'something went wrong building archive of project'
) )
) )
callback(null, archive) callback(null, archive)
return this.addAllDocsToArchive(project_id, archive, error => { this.addAllDocsToArchive(projectId, archive, error => {
if (error != null) { if (error) {
logger.error( logger.error(
{ err: error, project_id }, { err: error, projectId },
'error adding docs to zip stream' 'error adding docs to zip stream'
) )
} }
return this.addAllFilesToArchive(project_id, archive, error => { this.addAllFilesToArchive(projectId, archive, error => {
if (error != null) { if (error) {
logger.error( logger.error(
{ err: error, project_id }, { err: error, projectId },
'error adding files to zip stream' 'error adding files to zip stream'
) )
} }
return archive.finalize() archive.finalize()
}) })
}) })
}, },
addAllDocsToArchive(project_id, archive, callback) { addAllDocsToArchive(projectId, archive, callback) {
if (callback == null) { ProjectEntityHandler.getAllDocs(projectId, (error, docs) => {
callback = function (error) {} if (error) {
}
return ProjectEntityHandler.getAllDocs(project_id, function (error, docs) {
if (error != null) {
return callback(error) return callback(error)
} }
const jobs = [] const jobs = Object.entries(docs).map(([path, doc]) => cb => {
for (const path in docs) { if (path[0] === '/') {
const doc = docs[path] path = path.slice(1)
;(function (path, doc) { }
if (path[0] === '/') { logger.log({ projectId }, 'Adding doc')
path = path.slice(1) archive.append(doc.lines.join('\n'), { name: path })
} setImmediate(cb)
return jobs.push(function (callback) { })
logger.log({ project_id }, 'Adding doc') async.series(jobs, callback)
archive.append(doc.lines.join('\n'), { name: path })
return callback()
})
})(path, doc)
}
return async.series(jobs, callback)
}) })
}, },
addAllFilesToArchive(project_id, archive, callback) { addAllFilesToArchive(projectId, archive, callback) {
if (callback == null) { ProjectEntityHandler.getAllFiles(projectId, (error, files) => {
callback = function (error) {} if (error) {
} return callback(error)
return ProjectEntityHandler.getAllFiles(
project_id,
function (error, files) {
if (error != null) {
return callback(error)
}
const jobs = []
for (const path in files) {
const file = files[path]
;((path, file) =>
jobs.push(callback =>
FileStoreHandler.getFileStream(
project_id,
file._id,
{},
function (error, stream) {
if (error != null) {
logger.warn(
{ err: error, project_id, file_id: file._id },
'something went wrong adding file to zip archive'
)
return callback(error)
}
if (path[0] === '/') {
path = path.slice(1)
}
archive.append(stream, { name: path })
return stream.on('end', () => callback())
}
)
))(path, file)
}
return async.parallelLimit(jobs, 5, callback)
} }
) const jobs = Object.entries(files).map(([path, file]) => cb => {
FileStoreHandler.getFileStream(
projectId,
file._id,
{},
(error, stream) => {
if (error) {
logger.warn(
{ err: error, projectId, file_id: file._id },
'something went wrong adding file to zip archive'
)
return cb(error)
}
if (path[0] === '/') {
path = path.slice(1)
}
archive.append(stream, { name: path })
stream.on('end', () => cb())
}
)
})
async.parallelLimit(jobs, 5, callback)
})
}, },
} }