LockManager.test.js 3.3 KB

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