Send output files on timeout

The unconventional use of callbacks to return both an error and data
after compilation created a subtle bug where the output files were
dropped by the LockManager in case of an error such as a timeout.

This prevented the frontend to show error logs when a timeout occurs,
creating confusion among users.

We now attach the output files to the error so that they reach the
controller and are sent back to the web service.
This commit is contained in:
Eric Mc Sween
2019-10-22 15:30:14 -04:00
parent a62ff6e248
commit 775306aa63
4 changed files with 22 additions and 17 deletions

View File

@@ -26,21 +26,19 @@ module.exports = CompileController =
status = "terminated" status = "terminated"
else if error?.validate else if error?.validate
status = "validation-#{error.validate}" status = "validation-#{error.validate}"
else if error?.timedout
status = "timedout"
logger.log err: error, project_id: request.project_id, "timeout running compile"
else if error? else if error?
if error.timedout status = "error"
status = "timedout" code = 500
logger.log err: error, project_id: request.project_id, "timeout running compile" logger.warn err: error, project_id: request.project_id, "error running compile"
else
status = "error"
code = 500
logger.warn err: error, project_id: request.project_id, "error running compile"
else else
status = "failure" status = "failure"
for file in outputFiles for file in outputFiles
if file.path?.match(/output\.pdf$/) if file.path?.match(/output\.pdf$/)
status = "success" status = "success"
if status == "failure" if status == "failure"
logger.warn project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" logger.warn project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated"
@@ -49,6 +47,9 @@ module.exports = CompileController =
if file.path is "core" if file.path is "core"
logger.error project_id:request.project_id, req:req, outputFiles:outputFiles, "core file found in output" logger.error project_id:request.project_id, req:req, outputFiles:outputFiles, "core file found in output"
if error?
outputFiles = error.outputFiles || []
timer.done() timer.done()
res.status(code or 200).send { res.status(code or 200).send {
compile: compile:

View File

@@ -106,10 +106,11 @@ module.exports = CompileManager =
error = new Error("compilation") error = new Error("compilation")
error.validate = "fail" error.validate = "fail"
# compile was killed by user, was a validation, or a compile which failed validation # compile was killed by user, was a validation, or a compile which failed validation
if error?.terminated or error?.validate if error?.terminated or error?.validate or error?.timedout
OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) -> OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) ->
return callback(err) if err? return callback(err) if err?
callback(error, outputFiles) # return output files so user can check logs error.outputFiles = outputFiles # return output files so user can check logs
callback(error)
return return
# compile completed normally # compile completed normally
return callback(error) if error? return callback(error) if error?

View File

@@ -78,7 +78,7 @@ module.exports = DockerRunner =
_callback(args...) _callback(args...)
# Only call the callback once # Only call the callback once
_callback = () -> _callback = () ->
name = options.name name = options.name
streamEnded = false streamEnded = false
@@ -115,7 +115,7 @@ module.exports = DockerRunner =
_getContainerOptions: (command, image, volumes, timeout, environment) -> _getContainerOptions: (command, image, volumes, timeout, environment) ->
timeoutInSeconds = timeout / 1000 timeoutInSeconds = timeout / 1000
dockerVolumes = {} dockerVolumes = {}
for hostVol, dockerVol of volumes for hostVol, dockerVol of volumes
dockerVolumes[dockerVol] = {} dockerVolumes[dockerVol] = {}
@@ -148,7 +148,7 @@ module.exports = DockerRunner =
"Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}]
"CapDrop": "ALL" "CapDrop": "ALL"
"SecurityOpt": ["no-new-privileges"] "SecurityOpt": ["no-new-privileges"]
if Settings.path?.synctexBinHostPath? if Settings.path?.synctexBinHostPath?
options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro")
@@ -276,7 +276,7 @@ module.exports = DockerRunner =
logger.log container_id: containerId, "timeout reached, killing container" logger.log container_id: containerId, "timeout reached, killing container"
container.kill(() ->) container.kill(() ->)
, timeout , timeout
logger.log container_id: containerId, "waiting for docker container" logger.log container_id: containerId, "waiting for docker container"
container.wait (error, res) -> container.wait (error, res) ->
if error? if error?
@@ -355,4 +355,4 @@ module.exports = DockerRunner =
, oneHour = 60 * 60 * 1000 , oneHour = 60 * 60 * 1000
, randomDelay , randomDelay
DockerRunner.startContainerMonitor() DockerRunner.startContainerMonitor()

View File

@@ -15,7 +15,7 @@ describe "Timed out compile", ->
\\documentclass{article} \\documentclass{article}
\\begin{document} \\begin{document}
\\def\\x{Hello!\\par\\x} \\def\\x{Hello!\\par\\x}
\\x \\x
\\end{document} \\end{document}
''' '''
] ]
@@ -29,3 +29,6 @@ describe "Timed out compile", ->
it "should return a timedout status", -> it "should return a timedout status", ->
@body.compile.status.should.equal "timedout" @body.compile.status.should.equal "timedout"
it "should return the log output file name", ->
outputFilePaths = @body.compile.outputFiles.map((x) => x.path)
outputFilePaths.should.include('output.log')