Răsfoiți Sursa

[web] increase timeout for downloads from clsi (#31840)

* [web] increase timeout for downloads from clsi

* [web] detach request timeout from streaming timeout

* [web] align timeout for clsi-cache downloads with clsi downloads

* [web] destroy stream when bailing out early from aborted requests

GitOrigin-RevId: cc4cad9d684214b2eee0f5fc43513e430ceb0977
Jakob Ackermann 5 luni în urmă
părinte
comite
624a351aac

+ 19 - 5
services/web/app/src/Features/Compile/ClsiCacheController.mjs

@@ -53,7 +53,8 @@ async function _downloadFromCacheWithParams(
   filename
 ) {
   const userId = CompileController._getUserIdForCompile(req)
-  const signal = AbortSignal.timeout(60 * 1000)
+  const ac = new AbortController()
+  let timer = setTimeout(() => ac.abort(), 10_000)
   let location, projectName
   try {
     ;[{ location }, { name: projectName }] = await Promise.all([
@@ -62,11 +63,12 @@ async function _downloadFromCacheWithParams(
         userId,
         buildId,
         filename,
-        signal
+        ac.signal
       ),
       ProjectGetter.promises.getProject(projectId, { name: 1 }),
     ])
   } catch (err) {
+    clearTimeout(timer)
     if (err instanceof NotFoundError) {
       // res.sendStatus() sends a description of the status as body.
       // Using res.status().end() avoids sending that fake body.
@@ -76,11 +78,17 @@ async function _downloadFromCacheWithParams(
     }
   }
 
-  const { stream, response } = await fetchStreamWithResponse(location, {
-    signal,
-  })
+  let stream, response
+  try {
+    ;({ stream, response } = await fetchStreamWithResponse(location, {
+      signal: ac.signal,
+    }))
+  } finally {
+    clearTimeout(timer)
+  }
   if (req.destroyed) {
     // The client has disconnected already, avoid trying to write into the broken connection.
+    stream.destroy(new Error('user aborted the request'))
     return
   }
 
@@ -93,6 +101,10 @@ async function _downloadFromCacheWithParams(
       ? `${CompileController._getSafeProjectName({ name: projectName })}.pdf`
       : filename
   )
+  // Downloads can take a while on a slow connection, increase timeouts to 10min
+  const TEN_MINUTES_IN_MS = 10 * 60 * 1000
+  res.setTimeout(TEN_MINUTES_IN_MS)
+  timer = setTimeout(() => ac.abort(), TEN_MINUTES_IN_MS)
   try {
     res.writeHead(response.status)
     await pipeline(
@@ -132,6 +144,8 @@ async function _downloadFromCacheWithParams(
       },
       'CLSI-cache proxy error'
     )
+  } finally {
+    clearTimeout(timer)
   }
 }
 

+ 13 - 1
services/web/app/src/Features/Compile/CompileController.mjs

@@ -598,10 +598,12 @@ const _CompileController = {
       [0, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000, 45000, 60000]
     )
     Metrics.inc('proxy_to_clsi', 1, { path: action, status: 'start' })
+    const ac = new AbortController()
+    let timeout = setTimeout(() => ac.abort(), 10_000)
     try {
       const { stream, response } = await fetchStreamWithResponse(url.href, {
         method: req.method,
-        signal: AbortSignal.timeout(60 * 1000),
+        signal: ac.signal,
         headers: persistenceOptions.headers,
       })
       if (req.destroyed) {
@@ -610,6 +612,7 @@ const _CompileController = {
           path: action,
           status: 'req-aborted',
         })
+        stream.destroy(new Error('user aborted the request'))
         return
       }
       Metrics.inc('proxy_to_clsi', 1, {
@@ -622,6 +625,13 @@ const _CompileController = {
           res.setHeader(key, response.headers.get(key))
         }
       }
+
+      // Downloads can take a while on a slow connection, increase timeouts to 10min
+      const TEN_MINUTES_IN_MS = 10 * 60 * 1000
+      res.setTimeout(TEN_MINUTES_IN_MS)
+      clearTimeout(timeout)
+      timeout = setTimeout(() => ac.abort(), TEN_MINUTES_IN_MS)
+
       res.writeHead(response.status)
       await pipeline(stream, res)
       timer.labels.status = 'success'
@@ -679,6 +689,8 @@ const _CompileController = {
         },
         'CLSI proxy error'
       )
+    } finally {
+      clearTimeout(timeout)
     }
   },