getLockTests.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /* eslint-disable
  2. 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 chai = require('chai')
  18. const should = chai.should()
  19. const path = require('path')
  20. const modulePath = path.join(
  21. __dirname,
  22. '../../../../../app/src/infrastructure/LockManager.js'
  23. )
  24. const SandboxedModule = require('sandboxed-module')
  25. describe('LockManager - getting the lock', function() {
  26. beforeEach(function() {
  27. this.LockManager = SandboxedModule.require(modulePath, {
  28. globals: {
  29. console: console
  30. },
  31. requires: {
  32. 'logger-sharelatex': {
  33. log() {}
  34. },
  35. './RedisWrapper': {
  36. client() {
  37. return { auth() {} }
  38. }
  39. },
  40. 'settings-sharelatex': { redis: {} },
  41. 'metrics-sharelatex': {
  42. inc() {},
  43. gauge() {}
  44. }
  45. }
  46. })
  47. this.callback = sinon.stub()
  48. this.key = 'lock:web:lockName:project-id}'
  49. return (this.namespace = 'lockName')
  50. })
  51. describe('when the lock is not set', function() {
  52. beforeEach(function(done) {
  53. this.LockManager._tryLock = sinon.stub().yields(null, true)
  54. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  55. this.callback(...Array.from(args || []))
  56. return done()
  57. })
  58. })
  59. it('should try to get the lock', function() {
  60. return this.LockManager._tryLock
  61. .calledWith(this.key, this.namespace)
  62. .should.equal(true)
  63. })
  64. it('should only need to try once', function() {
  65. return this.LockManager._tryLock.callCount.should.equal(1)
  66. })
  67. it('should return the callback', function() {
  68. return this.callback.calledWith(null).should.equal(true)
  69. })
  70. })
  71. describe('when the lock is initially set', function() {
  72. beforeEach(function(done) {
  73. const startTime = Date.now()
  74. let tries = 0
  75. this.LockManager.LOCK_TEST_INTERVAL = 5
  76. this.LockManager._tryLock = function(key, namespace, callback) {
  77. if (callback == null) {
  78. callback = function(error, isFree) {}
  79. }
  80. if (Date.now() - startTime < 20 || tries < 2) {
  81. tries = tries + 1
  82. return callback(null, false)
  83. } else {
  84. return callback(null, true)
  85. }
  86. }
  87. sinon.spy(this.LockManager, '_tryLock')
  88. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  89. this.callback(...Array.from(args || []))
  90. return done()
  91. })
  92. })
  93. it('should call tryLock multiple times until free', function() {
  94. return (this.LockManager._tryLock.callCount > 1).should.equal(true)
  95. })
  96. it('should return the callback', function() {
  97. return this.callback.calledWith(null).should.equal(true)
  98. })
  99. })
  100. describe('when the lock times out', function() {
  101. beforeEach(function(done) {
  102. const time = Date.now()
  103. this.LockManager.MAX_LOCK_WAIT_TIME = 5
  104. this.LockManager._tryLock = sinon.stub().yields(null, false)
  105. return this.LockManager._getLock(this.key, this.namespace, (...args) => {
  106. this.callback(...Array.from(args || []))
  107. return done()
  108. })
  109. })
  110. it('should return the callback with an error', function() {
  111. this.callback.should.have.been.calledWith(
  112. sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Timeout'))
  113. )
  114. })
  115. })
  116. describe('when there are multiple requests for the same lock', function() {
  117. beforeEach(function(done) {
  118. let locked = false
  119. this.results = []
  120. this.LockManager.LOCK_TEST_INTERVAL = 1
  121. this.LockManager._tryLock = function(key, namespace, callback) {
  122. if (callback == null) {
  123. callback = function(error, gotLock, lockValue) {}
  124. }
  125. if (locked) {
  126. return callback(null, false)
  127. } else {
  128. locked = true // simulate getting the lock
  129. return callback(null, true)
  130. }
  131. }
  132. // Start ten lock requests in order at 1ms 2ms 3ms...
  133. // with them randomly holding the lock for 0-100ms.
  134. // Use predefined values for the random delay to make the test
  135. // deterministic.
  136. const randomDelays = [52, 45, 41, 84, 60, 81, 31, 46, 9, 43]
  137. let startTime = 0
  138. return Array.from(randomDelays).map((randomDelay, i) =>
  139. ((randomDelay, i) => {
  140. startTime += 1
  141. return setTimeout(() => {
  142. // changing the next line to the old method of LockManager._getLockByPolling
  143. // should give results in a random order and cause the test to fail.
  144. return this.LockManager._getLock(
  145. this.key,
  146. this.namespace,
  147. (...args) => {
  148. setTimeout(
  149. () => (locked = false), // release the lock after a random amount of time
  150. randomDelay
  151. )
  152. this.results.push(i)
  153. if (this.results.length === 10) {
  154. return done()
  155. }
  156. }
  157. )
  158. }, startTime)
  159. })(randomDelay, i)
  160. )
  161. })
  162. it('should process the requests in order', function() {
  163. return this.results.should.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  164. })
  165. })
  166. })