getLockTests.coffee 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. path = require('path')
  5. modulePath = path.join __dirname, '../../../../../app/js/infrastructure/LockManager.js'
  6. SandboxedModule = require('sandboxed-module')
  7. describe 'LockManager - getting the lock', ->
  8. beforeEach ->
  9. @LockManager = SandboxedModule.require modulePath, requires:
  10. "logger-sharelatex": log:->
  11. "./RedisWrapper":
  12. client: ()->
  13. auth:->
  14. "settings-sharelatex":{redis:{}}
  15. "metrics-sharelatex":
  16. inc:->
  17. gauge:->
  18. @callback = sinon.stub()
  19. @key = "lock:web:lockName:project-id}"
  20. @namespace = 'lockName'
  21. describe "when the lock is not set", ->
  22. beforeEach (done) ->
  23. @LockManager._tryLock = sinon.stub().yields(null, true)
  24. @LockManager._getLock @key, @namespace, (args...) =>
  25. @callback(args...)
  26. done()
  27. it "should try to get the lock", ->
  28. @LockManager._tryLock
  29. .calledWith(@key, @namespace)
  30. .should.equal true
  31. it "should only need to try once", ->
  32. @LockManager._tryLock.callCount.should.equal 1
  33. it "should return the callback", ->
  34. @callback.calledWith(null).should.equal true
  35. describe "when the lock is initially set", ->
  36. beforeEach (done) ->
  37. startTime = Date.now()
  38. tries = 0
  39. @LockManager.LOCK_TEST_INTERVAL = 5
  40. @LockManager._tryLock = (key, namespace, callback = (error, isFree) ->) ->
  41. if (Date.now() - startTime < 20) or (tries < 2)
  42. tries = tries + 1
  43. callback null, false
  44. else
  45. callback null, true
  46. sinon.spy @LockManager, "_tryLock"
  47. @LockManager._getLock @key, @namespace, (args...) =>
  48. @callback(args...)
  49. done()
  50. it "should call tryLock multiple times until free", ->
  51. (@LockManager._tryLock.callCount > 1).should.equal true
  52. it "should return the callback", ->
  53. @callback.calledWith(null).should.equal true
  54. describe "when the lock times out", ->
  55. beforeEach (done) ->
  56. time = Date.now()
  57. @LockManager.MAX_LOCK_WAIT_TIME = 5
  58. @LockManager._tryLock = sinon.stub().yields(null, false)
  59. @LockManager._getLock @key, @namespace, (args...) =>
  60. @callback(args...)
  61. done()
  62. it "should return the callback with an error", ->
  63. @callback.calledWith(new Error("timeout")).should.equal true
  64. describe "when there are multiple requests for the same lock", ->
  65. beforeEach (done) ->
  66. locked = false
  67. @results = []
  68. @LockManager.LOCK_TEST_INTERVAL = 1
  69. @LockManager._tryLock = (key, namespace, callback = (error, gotLock, lockValue) ->) ->
  70. if locked
  71. callback null, false
  72. else
  73. locked = true # simulate getting the lock
  74. callback null, true
  75. # Start ten lock requests in order at 1ms 2ms 3ms...
  76. # with them randomly holding the lock for 0-100ms.
  77. # Use predefined values for the random delay to make the test
  78. # deterministic.
  79. randomDelays = [52, 45, 41, 84, 60, 81, 31, 46, 9, 43 ]
  80. startTime = 0
  81. for randomDelay, i in randomDelays
  82. do (randomDelay, i) =>
  83. startTime += 1
  84. setTimeout () =>
  85. # changing the next line to the old method of LockManager._getLockByPolling
  86. # should give results in a random order and cause the test to fail.
  87. @LockManager._getLock @key, @namespace, (args...) =>
  88. setTimeout () ->
  89. locked = false # release the lock after a random amount of time
  90. , randomDelay
  91. @results.push i
  92. if @results.length is 10
  93. done()
  94. , startTime
  95. it "should process the requests in order", ->
  96. @results.should.deep.equal [0,1,2,3,4,5,6,7,8,9]