2022-07-07 08:27:20 -04:00
|
|
|
const { expect } = require('chai')
|
2020-02-19 06:15:37 -05:00
|
|
|
const sinon = require('sinon')
|
2024-08-05 07:14:56 -04:00
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
const modulePath = require('path').join(
|
|
|
|
__dirname,
|
|
|
|
'../../../app/js/LockManager'
|
|
|
|
)
|
2020-02-19 06:15:37 -05:00
|
|
|
const Errors = require('../../../app/js/Errors')
|
2020-02-19 06:15:08 -05:00
|
|
|
|
2022-07-07 08:27:20 -04:00
|
|
|
describe('LockManager', function () {
|
2020-08-10 12:01:11 -04:00
|
|
|
beforeEach(function () {
|
2024-01-11 03:13:11 -05:00
|
|
|
this.key = '/local/compile/directory'
|
2022-07-07 08:27:20 -04:00
|
|
|
this.clock = sinon.useFakeTimers()
|
2024-08-05 07:14:56 -04:00
|
|
|
this.LockManager = SandboxedModule.require(modulePath, {
|
|
|
|
requires: {
|
|
|
|
'@overleaf/metrics': (this.Metrics = {
|
|
|
|
inc: sinon.stub(),
|
|
|
|
gauge: sinon.stub(),
|
|
|
|
}),
|
|
|
|
'@overleaf/settings': (this.Settings = {
|
|
|
|
compileConcurrencyLimit: 5,
|
|
|
|
}),
|
|
|
|
'./Errors': (this.Erros = Errors),
|
|
|
|
},
|
|
|
|
})
|
2020-02-19 06:15:37 -05:00
|
|
|
})
|
2020-02-19 06:15:08 -05:00
|
|
|
|
2022-07-07 08:27:20 -04:00
|
|
|
afterEach(function () {
|
|
|
|
this.clock.restore()
|
|
|
|
})
|
2020-02-19 06:15:08 -05:00
|
|
|
|
2022-07-07 08:27:20 -04:00
|
|
|
describe('when the lock is available', function () {
|
2024-01-11 03:13:11 -05:00
|
|
|
it('the lock can be acquired', function () {
|
2024-08-05 07:14:56 -04:00
|
|
|
const lock = this.LockManager.acquire(this.key)
|
2024-01-11 03:13:11 -05:00
|
|
|
expect(lock).to.exist
|
|
|
|
lock.release()
|
2022-07-07 08:27:20 -04:00
|
|
|
})
|
|
|
|
})
|
2017-09-22 11:19:33 -04:00
|
|
|
|
2022-07-07 08:27:20 -04:00
|
|
|
describe('after the lock is acquired', function () {
|
2024-01-11 03:13:11 -05:00
|
|
|
beforeEach(function () {
|
2024-08-05 07:14:56 -04:00
|
|
|
this.lock = this.LockManager.acquire(this.key)
|
2024-01-11 03:13:11 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
if (this.lock != null) {
|
|
|
|
this.lock.release()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
it("the lock can't be acquired again", function () {
|
2024-08-05 07:14:56 -04:00
|
|
|
expect(() => this.LockManager.acquire(this.key)).to.throw(
|
2024-01-11 03:13:11 -05:00
|
|
|
Errors.AlreadyCompilingError
|
|
|
|
)
|
2020-02-19 06:15:37 -05:00
|
|
|
})
|
2020-02-19 06:15:08 -05:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
it('another lock can be acquired', function () {
|
2024-08-05 07:14:56 -04:00
|
|
|
const lock = this.LockManager.acquire('another key')
|
2024-01-11 03:13:11 -05:00
|
|
|
expect(lock).to.exist
|
|
|
|
lock.release()
|
2022-07-07 08:27:20 -04:00
|
|
|
})
|
2020-02-19 06:15:08 -05:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
it('the lock can be acquired again after an expiry period', function () {
|
|
|
|
// The expiry time is a little bit over 10 minutes. Let's wait 15 minutes.
|
|
|
|
this.clock.tick(15 * 60 * 1000)
|
2024-08-05 07:14:56 -04:00
|
|
|
this.lock = this.LockManager.acquire(this.key)
|
2024-01-11 03:13:11 -05:00
|
|
|
expect(this.lock).to.exist
|
2022-07-07 08:27:20 -04:00
|
|
|
})
|
2017-09-22 11:19:33 -04:00
|
|
|
|
2024-01-11 03:13:11 -05:00
|
|
|
it('the lock can be acquired again after it was released', function () {
|
2022-07-07 08:27:20 -04:00
|
|
|
this.lock.release()
|
2024-08-05 07:14:56 -04:00
|
|
|
this.lock = this.LockManager.acquire(this.key)
|
2024-01-11 03:13:11 -05:00
|
|
|
expect(this.lock).to.exist
|
2020-02-19 06:15:37 -05:00
|
|
|
})
|
|
|
|
})
|
2024-08-05 07:14:56 -04:00
|
|
|
|
|
|
|
describe('concurrency limit', function () {
|
|
|
|
it('exceeding the limit', function () {
|
|
|
|
for (let i = 0; i <= this.Settings.compileConcurrencyLimit; i++) {
|
2024-08-07 05:21:10 -04:00
|
|
|
this.LockManager.acquire('test_key' + i)
|
2024-08-05 07:14:56 -04:00
|
|
|
}
|
|
|
|
this.Metrics.inc
|
|
|
|
.calledWith('exceeded-compilier-concurrency-limit')
|
|
|
|
.should.equal(false)
|
|
|
|
expect(() =>
|
|
|
|
this.LockManager.acquire(
|
|
|
|
'test_key_' + (this.Settings.compileConcurrencyLimit + 1),
|
|
|
|
false
|
|
|
|
)
|
|
|
|
).to.throw(Errors.TooManyCompileRequestsError)
|
|
|
|
|
|
|
|
this.Metrics.inc
|
|
|
|
.calledWith('exceeded-compilier-concurrency-limit')
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('within the limit', function () {
|
|
|
|
for (let i = 0; i <= this.Settings.compileConcurrencyLimit - 1; i++) {
|
2024-08-07 05:21:10 -04:00
|
|
|
this.LockManager.acquire('test_key' + i)
|
2024-08-05 07:14:56 -04:00
|
|
|
}
|
|
|
|
this.Metrics.inc
|
|
|
|
.calledWith('exceeded-compilier-concurrency-limit')
|
|
|
|
.should.equal(false)
|
|
|
|
|
|
|
|
const lock = this.LockManager.acquire(
|
|
|
|
'test_key_' + this.Settings.compileConcurrencyLimit,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(lock.key).to.equal(
|
|
|
|
'test_key_' + this.Settings.compileConcurrencyLimit
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2020-02-19 06:15:37 -05:00
|
|
|
})
|