Browse Source

Merge pull request #43 from sharelatex/stop-compile

add support for stopping compile
Brian Gough 10 years ago
parent
commit
e4da985753

+ 2 - 0
services/clsi/app.coffee

@@ -56,6 +56,7 @@ app.param 'build_id', (req, res, next, build_id) ->
 
 
 app.post   "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile
+app.post   "/project/:project_id/compile/stop", CompileController.stopCompile
 app.delete "/project/:project_id", CompileController.clearCache
 
 app.get  "/project/:project_id/sync/code", CompileController.syncFromCode
@@ -65,6 +66,7 @@ app.get  "/project/:project_id/status", CompileController.status
 
 # Per-user containers
 app.post   "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile
+app.post   "/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile
 app.delete "/project/:project_id/user/:user_id", CompileController.clearCache
 
 app.get  "/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode

+ 21 - 3
services/clsi/app/coffee/CommandRunner.coffee

@@ -9,9 +9,27 @@ module.exports = CommandRunner =
 		logger.log project_id: project_id, command: command, directory: directory, "running command"
 		logger.warn "timeouts and sandboxing are not enabled with CommandRunner"
 
-		proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory
+		# run command as detached process so it has its own process group (which can be killed if needed)
+		proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true
+
 		proc.on "error", (err)->
 			logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command"
 			callback(err)
-		proc.on "close", () ->
-			callback()
+
+		proc.on "close", (code, signal) ->
+			logger.info code:code, signal:signal, project_id:project_id, "command exited"
+			if signal is 'SIGTERM' # signal from kill method below
+				err = new Error("terminated")
+				err.terminated = true
+				return callback(err)
+			else
+				callback()
+
+		return proc.pid # return process id to allow job to be killed if necessary
+
+	kill: (pid, callback = (error) ->) ->
+		try
+			process.kill -pid # kill all processes in group
+		catch err
+			return callback(err)
+		callback()

+ 10 - 2
services/clsi/app/coffee/CompileController.coffee

@@ -15,7 +15,9 @@ module.exports = CompileController =
 			ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) ->
 				return next(error) if error?
 				CompileManager.doCompile request, (error, outputFiles = []) ->
-					if error?
+					if error?.terminated
+						status = "terminated"
+					else if error?
 						logger.error err: error, project_id: request.project_id, "error running compile"
 						if error.timedout
 							status = "timedout"
@@ -43,7 +45,13 @@ module.exports = CompileController =
 								type: file.type
 								build: file.build
 					}
-		
+
+	stopCompile: (req, res, next) ->
+		{project_id, user_id} = req.params
+		CompileManager.stopCompile project_id, user_id, (error) ->
+			return next(error) if error?
+			res.sendStatus(204)
+
 	clearCache: (req, res, next = (error) ->) ->
 		ProjectPersistenceManager.clearProject req.params.project_id, req.params.user_id, (error) ->
 			return next(error) if error?

+ 11 - 0
services/clsi/app/coffee/CompileManager.coffee

@@ -58,6 +58,13 @@ module.exports = CompileManager =
 					timeout:   request.timeout
 					image:     request.imageName
 				}, (error, output, stats, timings) ->
+					# compile was killed by user
+					if error?.terminated
+						OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) ->
+							return callback(err) if err?
+							callback(error, outputFiles) # return output files so user can check logs
+						return
+					# compile completed normally
 					return callback(error) if error?
 					Metrics.inc("compiles-succeeded")
 					for metric_key, metric_value of stats or {}
@@ -78,6 +85,10 @@ module.exports = CompileManager =
 						OutputCacheManager.saveOutputFiles outputFiles, compileDir,  (error, newOutputFiles) ->
 							callback null, newOutputFiles
 	
+	stopCompile: (project_id, user_id, callback = (error) ->) ->
+		compileName = getCompileName(project_id, user_id)
+		LatexRunner.killLatex compileName, callback
+
 	clearProject: (project_id, user_id, _callback = (error) ->) ->
 		callback = (error) ->
 			_callback(error)

+ 14 - 1
services/clsi/app/coffee/LatexRunner.coffee

@@ -4,6 +4,8 @@ logger = require "logger-sharelatex"
 Metrics = require "./Metrics"
 CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner")
 
+ProcessTable = {}  # table of currently running jobs (pids or docker container names)
+
 module.exports = LatexRunner =
 	runLatex: (project_id, options, callback = (error) ->) ->
 		{directory, mainFile, compiler, timeout, image} = options
@@ -30,7 +32,10 @@ module.exports = LatexRunner =
 		if Settings.clsi?.strace
 			command = ["strace", "-o", "strace", "-ff"].concat(command)
 
-		CommandRunner.run project_id, command, directory, image, timeout, (error, output) ->
+		id = "#{project_id}" # record running project under this id
+
+		ProcessTable[id] = CommandRunner.run project_id, command, directory, image, timeout, (error, output) ->
+			delete ProcessTable[id]
 			return callback(error) if error?
 			runs = output?.stderr?.match(/^Run number \d+ of .*latex/mg)?.length or 0
 			failed = if output?.stdout?.match(/^Latexmk: Errors/m)? then 1 else 0
@@ -49,6 +54,14 @@ module.exports = LatexRunner =
 			timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0
 			callback error, output, stats, timings
 
+	killLatex: (project_id, callback = (error) ->) ->
+		id = "#{project_id}"
+		logger.log {id:id}, "killing running compile"
+		if not ProcessTable[id]?
+			return callback new Error("no such project to kill")
+		else
+			CommandRunner.kill ProcessTable[id], callback
+
 	_latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat(
 			["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"]
 		)