LockManager.js 1.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. const logger = require('@overleaf/logger')
  2. const Errors = require('./Errors')
  3. const RequestParser = require('./RequestParser')
  4. const Metrics = require('@overleaf/metrics')
  5. // The lock timeout should be higher than the maximum end-to-end compile time.
  6. // Here, we use the maximum compile timeout plus 2 minutes.
  7. const LOCK_TIMEOUT_MS = RequestParser.MAX_TIMEOUT * 1000 + 120000
  8. const LOCKS = new Map()
  9. function acquire(key) {
  10. const currentLock = LOCKS.get(key)
  11. if (currentLock != null) {
  12. if (currentLock.isExpired()) {
  13. logger.warn({ key }, 'Compile lock expired')
  14. currentLock.release()
  15. } else {
  16. throw new Errors.AlreadyCompilingError('compile in progress')
  17. }
  18. }
  19. Metrics.gauge('concurrent_compile_requests', LOCKS.size)
  20. const lock = new Lock(key)
  21. LOCKS.set(key, lock)
  22. return lock
  23. }
  24. class Lock {
  25. constructor(key) {
  26. this.key = key
  27. this.expiresAt = Date.now() + LOCK_TIMEOUT_MS
  28. }
  29. isExpired() {
  30. return Date.now() >= this.expiresAt
  31. }
  32. release() {
  33. const lockWasActive = LOCKS.delete(this.key)
  34. if (!lockWasActive) {
  35. logger.error({ key: this.key }, 'Lock was released twice')
  36. }
  37. if (this.isExpired()) {
  38. Metrics.inc('compile_lock_expired_before_release')
  39. }
  40. }
  41. }
  42. module.exports = { acquire }