getLockTests.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. const sinon = require('sinon')
  17. const path = require('path')
  18. const modulePath = path.join(
  19. __dirname,
  20. '../../../../../app/src/infrastructure/LockManager.js'
  21. )
  22. const SandboxedModule = require('sandboxed-module')
  23. describe('LockManager - getting the lock', function () {
  24. beforeEach(function () {
  25. this.LockManager = SandboxedModule.require(modulePath, {
  26. requires: {
  27. './RedisWrapper': {
  28. client() {
  29. return { auth() {} }
  30. },
  31. },
  32. '@overleaf/settings': {
  33. redis: {},
  34. lockManager: {
  35. lockTestInterval: 50,
  36. maxTestInterval: 1000,
  37. maxLockWaitTime: 10000,
  38. redisLockExpiry: 30,
  39. slowExecutionThreshold: 5000,
  40. },
  41. },
  42. '@overleaf/metrics': {
  43. inc() {},
  44. gauge() {},
  45. },
  46. },
  47. })
  48. this.callback = sinon.stub()
  49. this.key = 'lock:web:lockName:project-id}'
  50. return (this.namespace = 'lockName')
  51. })
  52. describe('when the lock is not set', function () {
  53. beforeEach(function (done) {
  54. this.LockManager._tryLock = sinon.stub().yields(null, true)
  55. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  56. this.callback(...Array.from(args || []))
  57. return done()
  58. })
  59. })
  60. it('should try to get the lock', function () {
  61. return this.LockManager._tryLock
  62. .calledWith(this.key, this.namespace)
  63. .should.equal(true)
  64. })
  65. it('should only need to try once', function () {
  66. return this.LockManager._tryLock.callCount.should.equal(1)
  67. })
  68. it('should return the callback', function () {
  69. return this.callback.calledWith(null).should.equal(true)
  70. })
  71. it('should clear the lock queue', function () {
  72. this.LockManager._lockQueuesSize().should.equal(0)
  73. })
  74. })
  75. describe('when the lock is initially set', function () {
  76. beforeEach(function (done) {
  77. const startTime = Date.now()
  78. let tries = 0
  79. this.LockManager.LOCK_TEST_INTERVAL = 5
  80. this.LockManager._tryLock = function (key, namespace, callback) {
  81. if (callback == null) {
  82. callback = function () {}
  83. }
  84. if (Date.now() - startTime < 20 || tries < 2) {
  85. tries = tries + 1
  86. return callback(null, false)
  87. } else {
  88. return callback(null, true)
  89. }
  90. }
  91. sinon.spy(this.LockManager, '_tryLock')
  92. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  93. this.callback(...Array.from(args || []))
  94. return done()
  95. })
  96. })
  97. it('should call tryLock multiple times until free', function () {
  98. return (this.LockManager._tryLock.callCount > 1).should.equal(true)
  99. })
  100. it('should return the callback', function () {
  101. return this.callback.calledWith(null).should.equal(true)
  102. })
  103. it('should clear the lock queue', function () {
  104. this.LockManager._lockQueuesSize().should.equal(0)
  105. })
  106. })
  107. describe('when the lock times out', function () {
  108. beforeEach(function (done) {
  109. const time = Date.now()
  110. this.LockManager.LOCK_TEST_INTERVAL = 1
  111. this.LockManager.MAX_LOCK_WAIT_TIME = 5
  112. this.LockManager._tryLock = sinon.stub().yields(null, false)
  113. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  114. this.callback(...Array.from(args || []))
  115. return done()
  116. })
  117. })
  118. it('should return the callback with an error', function () {
  119. this.callback.should.have.been.calledWith(
  120. sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Timeout'))
  121. )
  122. })
  123. })
  124. describe('when there are multiple requests for the same lock', function () {
  125. beforeEach(function (done) {
  126. let locked = false
  127. this.results = []
  128. this.LockManager.LOCK_TEST_INTERVAL = 1
  129. this.LockManager._tryLock = function (key, namespace, callback) {
  130. if (callback == null) {
  131. callback = function () {}
  132. }
  133. if (locked) {
  134. return callback(null, false)
  135. } else {
  136. locked = true // simulate getting the lock
  137. return callback(null, true)
  138. }
  139. }
  140. // Start ten lock requests in order at 1ms 2ms 3ms...
  141. // with them randomly holding the lock for 0-10ms.
  142. // Use predefined values for the random delay to make the test
  143. // deterministic.
  144. const randomDelays = [5, 4, 1, 8, 6, 8, 3, 4, 2, 4]
  145. let startTime = 0
  146. return Array.from(randomDelays).map((randomDelay, i) =>
  147. ((randomDelay, i) => {
  148. startTime += 1
  149. return setTimeout(() => {
  150. // changing the next line to the old method of LockManager._getLockByPolling
  151. // should give results in a random order and cause the test to fail.
  152. return this.LockManager._getLock(
  153. this.key,
  154. this.namespace,
  155. (...args) => {
  156. setTimeout(
  157. () => (locked = false), // release the lock after a random amount of time
  158. randomDelay
  159. )
  160. this.results.push(i)
  161. if (this.results.length === 10) {
  162. return done()
  163. }
  164. }
  165. )
  166. }, startTime)
  167. })(randomDelay, i)
  168. )
  169. })
  170. it('should process the requests in order', function () {
  171. return this.results.should.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  172. })
  173. it('should clear the lock queue', function () {
  174. this.LockManager._lockQueuesSize().should.equal(0)
  175. })
  176. })
  177. })