LockManagerTests.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. const { expect } = require('chai')
  2. const sinon = require('sinon')
  3. const SandboxedModule = require('sandboxed-module')
  4. const modulePath = require('node:path').join(
  5. __dirname,
  6. '../../../app/js/LockManager'
  7. )
  8. const Errors = require('../../../app/js/Errors')
  9. describe('LockManager', function () {
  10. beforeEach(function () {
  11. this.key = '/local/compile/directory'
  12. this.clock = sinon.useFakeTimers()
  13. this.LockManager = SandboxedModule.require(modulePath, {
  14. requires: {
  15. '@overleaf/metrics': (this.Metrics = {
  16. inc: sinon.stub(),
  17. gauge: sinon.stub(),
  18. }),
  19. '@overleaf/settings': (this.Settings = {
  20. compileConcurrencyLimit: 5,
  21. }),
  22. './Errors': (this.Erros = Errors),
  23. './RequestParser': { MAX_TIMEOUT: 600 },
  24. },
  25. })
  26. })
  27. afterEach(function () {
  28. this.clock.restore()
  29. })
  30. describe('when the lock is available', function () {
  31. it('the lock can be acquired', function () {
  32. const lock = this.LockManager.acquire(this.key)
  33. expect(lock).to.exist
  34. lock.release()
  35. })
  36. })
  37. describe('after the lock is acquired', function () {
  38. beforeEach(function () {
  39. this.lock = this.LockManager.acquire(this.key)
  40. })
  41. afterEach(function () {
  42. if (this.lock != null) {
  43. this.lock.release()
  44. }
  45. })
  46. it("the lock can't be acquired again", function () {
  47. expect(() => this.LockManager.acquire(this.key)).to.throw(
  48. Errors.AlreadyCompilingError
  49. )
  50. })
  51. it('another lock can be acquired', function () {
  52. const lock = this.LockManager.acquire('another key')
  53. expect(lock).to.exist
  54. lock.release()
  55. })
  56. it('the lock can be acquired again after an expiry period', function () {
  57. // The expiry time is a little bit over 10 minutes. Let's wait 15 minutes.
  58. this.clock.tick(15 * 60 * 1000)
  59. this.lock = this.LockManager.acquire(this.key)
  60. expect(this.lock).to.exist
  61. })
  62. it('the lock can be acquired again after it was released', function () {
  63. this.lock.release()
  64. this.lock = this.LockManager.acquire(this.key)
  65. expect(this.lock).to.exist
  66. })
  67. })
  68. describe('concurrency limit', function () {
  69. it('exceeding the limit', function () {
  70. for (let i = 0; i <= this.Settings.compileConcurrencyLimit; i++) {
  71. this.LockManager.acquire('test_key' + i)
  72. }
  73. this.Metrics.inc
  74. .calledWith('exceeded-compilier-concurrency-limit')
  75. .should.equal(false)
  76. expect(() =>
  77. this.LockManager.acquire(
  78. 'test_key_' + (this.Settings.compileConcurrencyLimit + 1),
  79. false
  80. )
  81. ).to.throw(Errors.TooManyCompileRequestsError)
  82. this.Metrics.inc
  83. .calledWith('exceeded-compilier-concurrency-limit')
  84. .should.equal(true)
  85. })
  86. it('within the limit', function () {
  87. for (let i = 0; i <= this.Settings.compileConcurrencyLimit - 1; i++) {
  88. this.LockManager.acquire('test_key' + i)
  89. }
  90. this.Metrics.inc
  91. .calledWith('exceeded-compilier-concurrency-limit')
  92. .should.equal(false)
  93. const lock = this.LockManager.acquire(
  94. 'test_key_' + this.Settings.compileConcurrencyLimit,
  95. false
  96. )
  97. expect(lock.key).to.equal(
  98. 'test_key_' + this.Settings.compileConcurrencyLimit
  99. )
  100. })
  101. })
  102. })