Files
overleaf-cep/services/clsi/app/js/LockManager.js
Andrew Rumble cd7da983d1 Merge pull request #30232 from overleaf/ar/convert-clsi-to-es-modules
[clsi] convert to ES modules

GitOrigin-RevId: fb7fa52cc8f678ee31be352e62a5dff95e88008b
2026-01-22 09:06:23 +00:00

67 lines
1.6 KiB
JavaScript

import logger from '@overleaf/logger'
import Errors from './Errors.js'
import RequestParser from './RequestParser.js'
import Metrics from '@overleaf/metrics'
import Settings from '@overleaf/settings'
// The lock timeout should be higher than the maximum end-to-end compile time.
// Here, we use the maximum compile timeout plus 2 minutes.
const LOCK_TIMEOUT_MS = RequestParser.MAX_TIMEOUT * 1000 + 120000
const LOCKS = new Map()
function acquire(key) {
const currentLock = LOCKS.get(key)
if (currentLock != null) {
if (currentLock.isExpired()) {
logger.warn({ key }, 'Compile lock expired')
currentLock.release()
} else {
throw new Errors.AlreadyCompilingError('compile in progress')
}
}
checkConcurrencyLimit()
const lock = new Lock(key)
LOCKS.set(key, lock)
return lock
}
function checkConcurrencyLimit() {
Metrics.gauge('concurrent_compile_requests', LOCKS.size)
if (LOCKS.size <= Settings.compileConcurrencyLimit) {
return
}
Metrics.inc('exceeded-compilier-concurrency-limit')
throw new Errors.TooManyCompileRequestsError(
'too many concurrent compile requests'
)
}
class Lock {
constructor(key) {
this.key = key
this.expiresAt = Date.now() + LOCK_TIMEOUT_MS
}
isExpired() {
return Date.now() >= this.expiresAt
}
release() {
const lockWasActive = LOCKS.delete(this.key)
if (!lockWasActive) {
logger.error({ key: this.key }, 'Lock was released twice')
}
if (this.isExpired()) {
Metrics.inc('compile_lock_expired_before_release')
}
}
}
export default { acquire }