8ed650f57a
* [web] stopOnFirstError=true does not conflict with =false locally Allow stopOnFirstError to be enabled in the compile from cache and disabled locally. Compiles that passed with stopOnFirstError=true will also pass with stopOnFirstError=false. The inverse does not hold, and we need to recompile. * [web] record event when using compile from cache * [web] record event when falling back to clsi-cache * [web] make clsi-cache a premium feature * [k8s] clsi-cache: increase disk size for beta rollout NOTE: As this is a premium feature and paid servers run in zones c+d, we do not need to scale up clsi-cache in zone b for now. * [web] enable full sampling of compile-result-backend events * [web] fix frontend tests * [web] be more verbose when determining access to clsi-cache feature GitOrigin-RevId: 6fd663e16085187876eb225f7f33eeeaf69d2b2a
112 lines
2.9 KiB
JavaScript
112 lines
2.9 KiB
JavaScript
const { NotFoundError } = 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')
|
|
|
|
/**
|
|
* Get the most recent build and metadata
|
|
*
|
|
* Internal: internal metadata; External: fine to send to user as-is.
|
|
*
|
|
* @param projectId
|
|
* @param userId
|
|
* @param filename
|
|
* @param signal
|
|
* @return {Promise<{internal: {zone: string, location: string}, external: {isUpToDate: boolean, lastUpdated: Date, size: number, allFiles: string[]}}>}
|
|
*/
|
|
async function getLatestBuildFromCache(projectId, userId, filename, signal) {
|
|
const [
|
|
{ location, lastModified: lastCompiled, zone, size, allFiles },
|
|
lastUpdatedInRedis,
|
|
{ lastUpdated: lastUpdatedInMongo },
|
|
] = await Promise.all([
|
|
ClsiCacheHandler.getLatestOutputFile(projectId, userId, filename, signal),
|
|
DocumentUpdaterHandler.promises.getProjectLastUpdatedAt(projectId),
|
|
ProjectGetter.promises.getProject(projectId, { lastUpdated: 1 }),
|
|
])
|
|
|
|
const lastUpdated =
|
|
lastUpdatedInRedis > lastUpdatedInMongo
|
|
? lastUpdatedInRedis
|
|
: lastUpdatedInMongo
|
|
const isUpToDate = lastCompiled >= lastUpdated
|
|
|
|
return {
|
|
internal: {
|
|
location,
|
|
zone,
|
|
},
|
|
external: {
|
|
isUpToDate,
|
|
lastUpdated,
|
|
size,
|
|
allFiles,
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Collect metadata and prepare the clsi-cache for the given project.
|
|
*
|
|
* @param projectId
|
|
* @param userId
|
|
* @param sourceProjectId
|
|
* @param templateId
|
|
* @param templateVersionId
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function prepareClsiCache(
|
|
projectId,
|
|
userId,
|
|
{ sourceProjectId, templateId, templateVersionId }
|
|
) {
|
|
const { variant } = await SplitTestHandler.promises.getAssignmentForUser(
|
|
userId,
|
|
'copy-clsi-cache'
|
|
)
|
|
if (variant !== 'enabled') return
|
|
|
|
const features = await UserGetter.promises.getUserFeatures(userId)
|
|
if (features.compileGroup !== 'priority') return
|
|
|
|
const signal = AbortSignal.timeout(5_000)
|
|
let lastUpdated
|
|
let zone = 'b' // populate template data on zone b
|
|
if (sourceProjectId) {
|
|
try {
|
|
;({
|
|
internal: { zone },
|
|
external: { lastUpdated },
|
|
} = await getLatestBuildFromCache(
|
|
sourceProjectId,
|
|
userId,
|
|
'output.tar.gz',
|
|
signal
|
|
))
|
|
} catch (err) {
|
|
if (err instanceof NotFoundError) return // nothing cached yet
|
|
throw err
|
|
}
|
|
}
|
|
try {
|
|
await ClsiCacheHandler.prepareCacheSource(projectId, userId, {
|
|
sourceProjectId,
|
|
templateId,
|
|
templateVersionId,
|
|
zone,
|
|
lastUpdated,
|
|
signal,
|
|
})
|
|
} catch (err) {
|
|
if (err instanceof NotFoundError) return // nothing cached yet/expired.
|
|
throw err
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getLatestBuildFromCache,
|
|
prepareClsiCache,
|
|
}
|