mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] retry fetching initial compile from cache response (#25436)
* [web] move building of compile from cache response into manager * [web] retry fetching initial compile from cache response GitOrigin-RevId: b4dc89f1b91d99e869c0c7789881dc72d8a5761f
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
const { NotFoundError } = require('../Errors/Errors')
|
||||
const { NotFoundError, ResourceGoneError } = require('../Errors/Errors')
|
||||
const {
|
||||
fetchStreamWithResponse,
|
||||
RequestFailedError,
|
||||
fetchJson,
|
||||
} = require('@overleaf/fetch-utils')
|
||||
const Path = require('path')
|
||||
const { pipeline } = require('stream/promises')
|
||||
@@ -110,61 +109,14 @@ async function getLatestBuildFromCache(req, res) {
|
||||
const userId = CompileController._getUserIdForCompile(req)
|
||||
try {
|
||||
const {
|
||||
internal: { location: metaLocation },
|
||||
external: { isUpToDate, allFiles, zone, shard },
|
||||
} = await ClsiCacheManager.getLatestBuildFromCache(
|
||||
projectId,
|
||||
userId,
|
||||
'output.overleaf.json'
|
||||
)
|
||||
zone,
|
||||
outputFiles,
|
||||
compileGroup,
|
||||
clsiServerId,
|
||||
clsiCacheShard,
|
||||
options,
|
||||
} = await ClsiCacheManager.getLatestCompileResult(projectId, userId)
|
||||
|
||||
if (!isUpToDate) return res.sendStatus(410)
|
||||
|
||||
const meta = await fetchJson(metaLocation, {
|
||||
signal: AbortSignal.timeout(5 * 1000),
|
||||
})
|
||||
|
||||
const [, editorId, buildId] = metaLocation.match(
|
||||
/\/build\/([a-f0-9-]+?)-([a-f0-9]+-[a-f0-9]+)\//
|
||||
)
|
||||
|
||||
let baseURL = `/project/${projectId}`
|
||||
if (userId) {
|
||||
baseURL += `/user/${userId}`
|
||||
}
|
||||
|
||||
const { ranges, contentId, clsiServerId, compileGroup, size, options } =
|
||||
meta
|
||||
|
||||
const outputFiles = allFiles
|
||||
.filter(
|
||||
path => path !== 'output.overleaf.json' && path !== 'output.tar.gz'
|
||||
)
|
||||
.map(path => {
|
||||
const f = {
|
||||
url: `${baseURL}/build/${editorId}-${buildId}/output/${path}`,
|
||||
downloadURL: `/download/project/${projectId}/build/${editorId}-${buildId}/output/cached/${path}`,
|
||||
build: buildId,
|
||||
path,
|
||||
type: path.split('.').pop(),
|
||||
}
|
||||
if (path === 'output.pdf') {
|
||||
Object.assign(f, {
|
||||
size,
|
||||
editorId,
|
||||
})
|
||||
if (clsiServerId !== shard) {
|
||||
// Enable PDF caching and attempt to download from VM first.
|
||||
// (clsi VMs do not have the editorId in the path on disk, omit it).
|
||||
Object.assign(f, {
|
||||
url: `${baseURL}/build/${buildId}/output/output.pdf`,
|
||||
ranges,
|
||||
contentId,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f
|
||||
})
|
||||
let { pdfCachingMinChunkSize, pdfDownloadDomain } =
|
||||
await CompileController._getSplitTestOptions(req, res)
|
||||
pdfDownloadDomain += `/zone/${zone}`
|
||||
@@ -174,7 +126,7 @@ async function getLatestBuildFromCache(req, res) {
|
||||
outputFiles,
|
||||
compileGroup,
|
||||
clsiServerId,
|
||||
clsiCacheShard: shard,
|
||||
clsiCacheShard,
|
||||
pdfDownloadDomain,
|
||||
pdfCachingMinChunkSize,
|
||||
options,
|
||||
@@ -182,6 +134,8 @@ async function getLatestBuildFromCache(req, res) {
|
||||
} catch (err) {
|
||||
if (err instanceof NotFoundError) {
|
||||
res.sendStatus(404)
|
||||
} else if (err instanceof ResourceGoneError) {
|
||||
res.sendStatus(410)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const _ = require('lodash')
|
||||
const { NotFoundError } = require('../Errors/Errors')
|
||||
const { NotFoundError, ResourceGoneError } = require('../Errors/Errors')
|
||||
const ClsiCacheHandler = require('./ClsiCacheHandler')
|
||||
const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||
const ProjectGetter = require('../Project/ProjectGetter')
|
||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { fetchJson, RequestFailedError } = require('@overleaf/fetch-utils')
|
||||
|
||||
/**
|
||||
* Get the most recent build and metadata
|
||||
@@ -50,6 +51,98 @@ async function getLatestBuildFromCache(projectId, userId, filename, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
class MetaFileExpiredError extends NotFoundError {}
|
||||
|
||||
async function getLatestCompileResult(projectId, userId) {
|
||||
const signal = AbortSignal.timeout(15_000)
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
return await tryGetLatestCompileResult(projectId, userId, signal)
|
||||
} catch (err) {
|
||||
if (err instanceof MetaFileExpiredError) {
|
||||
continue
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
throw new NotFoundError()
|
||||
}
|
||||
|
||||
async function tryGetLatestCompileResult(projectId, userId, signal) {
|
||||
const {
|
||||
internal: { location: metaLocation },
|
||||
external: { isUpToDate, allFiles, zone, shard: clsiCacheShard },
|
||||
} = await getLatestBuildFromCache(
|
||||
projectId,
|
||||
userId,
|
||||
'output.overleaf.json',
|
||||
signal
|
||||
)
|
||||
if (!isUpToDate) throw new ResourceGoneError()
|
||||
|
||||
let meta
|
||||
try {
|
||||
meta = await fetchJson(metaLocation, {
|
||||
signal: AbortSignal.timeout(5 * 1000),
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError && err.response.status === 404) {
|
||||
throw new MetaFileExpiredError(
|
||||
'build expired between listing and reading'
|
||||
)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
const [, editorId, buildId] = metaLocation.match(
|
||||
/\/build\/([a-f0-9-]+?)-([a-f0-9]+-[a-f0-9]+)\//
|
||||
)
|
||||
const { ranges, contentId, clsiServerId, compileGroup, size, options } = meta
|
||||
|
||||
let baseURL = `/project/${projectId}`
|
||||
if (userId) {
|
||||
baseURL += `/user/${userId}`
|
||||
}
|
||||
|
||||
const outputFiles = allFiles
|
||||
.filter(path => path !== 'output.overleaf.json' && path !== 'output.tar.gz')
|
||||
.map(path => {
|
||||
const f = {
|
||||
url: `${baseURL}/build/${editorId}-${buildId}/output/${path}`,
|
||||
downloadURL: `/download/project/${projectId}/build/${editorId}-${buildId}/output/cached/${path}`,
|
||||
build: buildId,
|
||||
path,
|
||||
type: path.split('.').pop(),
|
||||
}
|
||||
if (path === 'output.pdf') {
|
||||
Object.assign(f, {
|
||||
size,
|
||||
editorId,
|
||||
})
|
||||
if (clsiServerId !== clsiCacheShard) {
|
||||
// Enable PDF caching and attempt to download from VM first.
|
||||
// (clsi VMs do not have the editorId in the path on disk, omit it).
|
||||
Object.assign(f, {
|
||||
url: `${baseURL}/build/${buildId}/output/output.pdf`,
|
||||
ranges,
|
||||
contentId,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f
|
||||
})
|
||||
|
||||
return {
|
||||
allFiles,
|
||||
zone,
|
||||
outputFiles,
|
||||
compileGroup,
|
||||
clsiServerId,
|
||||
clsiCacheShard,
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect metadata and prepare the clsi-cache for the given project.
|
||||
*
|
||||
@@ -109,5 +202,6 @@ async function prepareClsiCache(
|
||||
|
||||
module.exports = {
|
||||
getLatestBuildFromCache,
|
||||
getLatestCompileResult,
|
||||
prepareClsiCache,
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ class ServiceNotConfiguredError extends BackwardCompatibleError {}
|
||||
|
||||
class TooManyRequestsError extends BackwardCompatibleError {}
|
||||
|
||||
class ResourceGoneError extends BackwardCompatibleError {}
|
||||
|
||||
class DuplicateNameError extends OError {}
|
||||
|
||||
class InvalidNameError extends BackwardCompatibleError {}
|
||||
@@ -319,6 +321,7 @@ module.exports = {
|
||||
ForbiddenError,
|
||||
ServiceNotConfiguredError,
|
||||
TooManyRequestsError,
|
||||
ResourceGoneError,
|
||||
DuplicateNameError,
|
||||
InvalidNameError,
|
||||
UnsupportedFileTypeError,
|
||||
|
||||
Reference in New Issue
Block a user