[web] set a few more explicit timeouts on fetch requests (#35402)

* [web] set a few more explicit timeouts on fetch requests

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
Co-authored-by: Brian Gough <brian.gough@overleaf.com>

* [web] update test mocks to expect signal

---------

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 356b97207f6e9a2628e54ff15b034a8e694a4524
This commit is contained in:
Jakob Ackermann
2026-09-17 11:24:31 +00:00
committed by Copybot
co-authored by Andrew Rumble Brian Gough
parent 05423f1a1d
commit ff72ccf5ba
4 changed files with 11 additions and 6 deletions
@@ -79,7 +79,7 @@ async function flushProject(projectId) {
try {
await fetchNothing(
`${settings.apis.project_history.url}/project/${projectId}/flush`,
{ method: 'POST' }
{ method: 'POST', signal: AbortSignal.timeout(60_000) }
)
} catch (err) {
throw OError.tag(err, 'failed to flush project to project history', {
@@ -142,7 +142,7 @@ async function _deleteProjectInProjectHistory(projectId) {
try {
await fetchNothing(
`${settings.apis.project_history.url}/project/${projectId}`,
{ method: 'DELETE' }
{ method: 'DELETE', signal: AbortSignal.timeout(5 * 60_000) }
)
} catch (err) {
throw OError.tag(
@@ -158,6 +158,7 @@ async function _deleteProjectInFullProjectHistory(historyId) {
await fetchNothing(`${HISTORY_V1_URL}/projects/${historyId}`, {
method: 'DELETE',
basicAuth: HISTORY_V1_BASIC_AUTH,
signal: AbortSignal.timeout(5 * 60_000),
})
} catch (err) {
throw OError.tag(err, 'failed to clear project history', { historyId })
@@ -202,7 +202,9 @@ async function streamConvertedProjectDocument({
clsiServerId ?? undefined
)
const { stream, response } = await fetchStreamWithResponse(downloadUrl)
const { stream, response } = await fetchStreamWithResponse(downloadUrl, {
signal: AbortSignal.timeout(10 * 60_000),
})
const contentLength = parseInt(response.headers.get('Content-Length'), 10)
return { stream, contentLength }
@@ -484,7 +484,7 @@ describe('HistoryManager', function () {
it('should call the project-history service', async function (ctx) {
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWith(
`${ctx.projectHistoryUrl}/project/${projectId}`,
{ method: 'DELETE' }
{ method: 'DELETE', signal: sinon.match.instanceOf(AbortSignal) }
)
})
@@ -497,6 +497,7 @@ describe('HistoryManager', function () {
user: ctx.v1HistoryUser,
password: ctx.v1HistoryPassword,
},
signal: sinon.match.instanceOf(AbortSignal),
}
)
})
@@ -479,14 +479,15 @@ describe('DocumentConversionManager', function () {
)
})
it('should GET the file from clsi-nginx with the clsiserverid query param', function (ctx) {
it('should GET the file from clsi-nginx with the a timeout and the clsiserverid query param', function (ctx) {
const expectedUrl = new URL(ctx.Settings.apis.clsi.downloadHost)
expectedUrl.pathname = `/project/${ctx.conversionId}/build/${ctx.buildId}/output/${ctx.file}`
expectedUrl.searchParams.set('clsiserverid', ctx.clsiServerId)
sinon.assert.calledWith(
ctx.fetchUtils.fetchStreamWithResponse,
sinon.match(url => url.toString() === expectedUrl.toString())
sinon.match(url => url.toString() === expectedUrl.toString()),
{ signal: sinon.match.instanceOf(AbortSignal) }
)
})