Merge pull request #26637 from overleaf/bg-clsi-fix-process-group-for-local-compiles

fix "stop compile" option for local command runner in CE/SP

GitOrigin-RevId: 7986b505362aaf33ac6e161b3b54458baba1e2e6
This commit is contained in:
Brian Gough
2025-07-10 17:33:41 +01:00
committed by Copybot
parent 4fe01f2bf1
commit f45031c6f9
5 changed files with 101 additions and 1 deletions

View File

@@ -9,6 +9,14 @@ export function throttledRecompile() {
return recompile
}
export function stopCompile(options: { delay?: number } = {}) {
const { delay = 0 } = options
cy.wait(delay)
cy.log('Stop compile')
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
cy.findByRole('menuitem', { name: 'Stop compilation' }).click()
}
export function prepareWaitForNextCompileSlot() {
let lastCompile = 0
function queueReset() {

View File

@@ -1,7 +1,7 @@
import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project'
import { isExcludedBySharding, startWith } from './helpers/config'
import { throttledRecompile } from './helpers/compile'
import { throttledRecompile, stopCompile } from './helpers/compile'
import { v4 as uuid } from 'uuid'
import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
@@ -56,8 +56,40 @@ describe('SandboxedCompiles', function () {
checkSyncTeX()
checkXeTeX()
checkRecompilesAfterErrors()
checkStopCompile()
})
function checkStopCompile() {
it('users can stop a running compile', function () {
login('user@example.com')
createProject('test-project')
// create an infinite loop in the main document
// this will cause the compile to run indefinitely
cy.findByText('\\maketitle').parent().click()
cy.findByText('\\maketitle')
.parent()
.type('\n\\def\\x{{}Hello!\\par\\x}\\x')
cy.log('Start compile')
// We need to start the compile manually because we do not want to wait for it to finish
cy.findByText('Recompile').click()
// Now stop the compile and kill the latex process
stopCompile({ delay: 1000 })
cy.get('.logs-pane')
.invoke('text')
.should('match', /PDF Rendering Error|Compilation cancelled/)
// Check that the previous compile is not running in the background by
// disabling the infinite loop and recompiling
cy.findByText('\\def').parent().click()
cy.findByText('\\def').parent().type('{home}disabled loop% ')
cy.findByText('Recompile').click()
cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
cy.get('.logs-pane').should(
'not.contain.text',
'A previous compile is still running'
)
})
}
function checkSyncTeX() {
// TODO(25342): re-enable
// eslint-disable-next-line mocha/no-skipped-tests
@@ -227,6 +259,7 @@ describe('SandboxedCompiles', function () {
checkSyncTeX()
checkXeTeX()
checkRecompilesAfterErrors()
checkStopCompile()
})
describe.skip('unavailable in CE', function () {
@@ -241,5 +274,6 @@ describe('SandboxedCompiles', function () {
checkSyncTeX()
checkXeTeX()
checkRecompilesAfterErrors()
checkStopCompile()
})
})

View File

@@ -54,6 +54,7 @@ module.exports = CommandRunner = {
cwd: directory,
env,
stdio: ['pipe', 'pipe', 'ignore'],
detached: true,
})
let stdout = ''

View File

@@ -0,0 +1,47 @@
const Client = require('./helpers/Client')
const ClsiApp = require('./helpers/ClsiApp')
const { expect } = require('chai')
describe('Stop compile', function () {
before(function (done) {
this.request = {
options: {
timeout: 100,
}, // seconds
resources: [
{
path: 'main.tex',
content: `\
\\documentclass{article}
\\begin{document}
\\def\\x{Hello!\\par\\x}
\\x
\\end{document}\
`,
},
],
}
this.project_id = Client.randomId()
ClsiApp.ensureRunning(() => {
// start the compile in the background
Client.compile(this.project_id, this.request, (error, res, body) => {
this.compileResult = { error, res, body }
})
// wait for 1 second before stopping the compile
setTimeout(() => {
Client.stopCompile(this.project_id, (error, res, body) => {
this.stopResult = { error, res, body }
setTimeout(done, 1000) // allow time for the compile request to terminate
})
}, 1000)
})
})
it('should force a compile response with an error status', function () {
expect(this.stopResult.error).to.be.null
expect(this.stopResult.res.statusCode).to.equal(204)
expect(this.compileResult.res.statusCode).to.equal(200)
expect(this.compileResult.body.compile.status).to.equal('terminated')
expect(this.compileResult.body.compile.error).to.equal('terminated')
})
})

View File

@@ -42,6 +42,16 @@ module.exports = Client = {
)
},
stopCompile(projectId, callback) {
if (callback == null) {
callback = function () {}
}
return request.post(
{ url: `${this.host}/project/${projectId}/compile/stop` },
callback
)
},
clearCache(projectId, callback) {
if (callback == null) {
callback = function () {}