2024-01-11 03:13:11 -05:00
|
|
|
const logger = require('@overleaf/logger')
|
2020-02-19 06:14:37 -05:00
|
|
|
const Errors = require('./Errors')
|
2024-01-11 03:13:11 -05:00
|
|
|
const RequestParser = require('./RequestParser')
|
2024-07-30 05:01:55 -04:00
|
|
|
const Metrics = require('@overleaf/metrics')
|
2024-08-05 07:14:56 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2017-09-22 11:19:33 -04:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
// 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
|
2022-07-07 08:27:20 -04:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
const LOCKS = new Map()
|
2022-07-07 08:27:20 -04:00
|
|
|
|
2024-08-07 05:21:10 -04:00
|
|
|
function acquire(key) {
|
2024-01-11 03:13:11 -05:00
|
|
|
const currentLock = LOCKS.get(key)
|
|
|
|
if (currentLock != null) {
|
|
|
|
if (currentLock.isExpired()) {
|
|
|
|
logger.warn({ key }, 'Compile lock expired')
|
|
|
|
currentLock.release()
|
2022-07-07 08:27:20 -04:00
|
|
|
} else {
|
2024-01-11 03:13:11 -05:00
|
|
|
throw new Errors.AlreadyCompilingError('compile in progress')
|
2020-02-19 06:14:37 -05:00
|
|
|
}
|
2022-07-07 08:27:20 -04:00
|
|
|
}
|
2024-01-11 03:13:11 -05:00
|
|
|
|
2024-08-07 05:21:10 -04:00
|
|
|
checkConcurrencyLimit()
|
2024-07-30 05:01:55 -04:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
const lock = new Lock(key)
|
|
|
|
LOCKS.set(key, lock)
|
|
|
|
return lock
|
2020-02-19 06:14:37 -05:00
|
|
|
}
|
2022-07-07 08:27:20 -04:00
|
|
|
|
2024-08-07 05:21:10 -04:00
|
|
|
function checkConcurrencyLimit() {
|
2024-08-05 07:14:56 -04:00
|
|
|
Metrics.gauge('concurrent_compile_requests', LOCKS.size)
|
|
|
|
|
|
|
|
if (LOCKS.size <= Settings.compileConcurrencyLimit) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
Metrics.inc('exceeded-compilier-concurrency-limit')
|
|
|
|
|
2024-08-07 05:21:10 -04:00
|
|
|
throw new Errors.TooManyCompileRequestsError(
|
|
|
|
'too many concurrent compile requests'
|
|
|
|
)
|
2024-08-05 07:14:56 -04:00
|
|
|
}
|
|
|
|
|
2022-07-07 08:27:20 -04:00
|
|
|
class Lock {
|
2024-01-11 03:13:11 -05:00
|
|
|
constructor(key) {
|
|
|
|
this.key = key
|
|
|
|
this.expiresAt = Date.now() + LOCK_TIMEOUT_MS
|
2022-07-07 08:27:20 -04:00
|
|
|
}
|
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
isExpired() {
|
|
|
|
return Date.now() >= this.expiresAt
|
2022-07-07 08:27:20 -04:00
|
|
|
}
|
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
release() {
|
|
|
|
const lockWasActive = LOCKS.delete(this.key)
|
|
|
|
if (!lockWasActive) {
|
|
|
|
logger.error({ key: this.key }, 'Lock was released twice')
|
|
|
|
}
|
2024-07-30 05:01:55 -04:00
|
|
|
if (this.isExpired()) {
|
|
|
|
Metrics.inc('compile_lock_expired_before_release')
|
|
|
|
}
|
2022-07-07 08:27:20 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = { acquire }
|