LockManager.coffee 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. Settings = require "settings-sharelatex"
  2. redis = require("redis-sharelatex")
  3. rclient = redis.createClient(Settings.redis.web)
  4. os = require "os"
  5. crypto = require "crypto"
  6. HOST = os.hostname()
  7. PID = process.pid
  8. RND = crypto.randomBytes(4).toString('hex')
  9. COUNT = 0
  10. module.exports = LockManager =
  11. LOCK_TEST_INTERVAL: 50 # 50ms between each test of the lock
  12. MAX_LOCK_WAIT_TIME: 10000 # 10s maximum time to spend trying to get the lock
  13. LOCK_TTL: 300 # seconds (allow 5 minutes for any operation to complete)
  14. # Use a signed lock value as described in
  15. # http://redis.io/topics/distlock#correct-implementation-with-a-single-instance
  16. # to prevent accidental unlocking by multiple processes
  17. randomLock : () ->
  18. time = Date.now()
  19. return "locked:host=#{HOST}:pid=#{PID}:random=#{RND}:time=#{time}:count=#{COUNT++}"
  20. unlockScript: 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end';
  21. tryLock : (key, callback = (err, gotLock) ->) ->
  22. lockValue = LockManager.randomLock()
  23. rclient.set key, lockValue, "EX", @LOCK_TTL, "NX", (err, gotLock)->
  24. return callback(err) if err?
  25. if gotLock == "OK"
  26. callback err, true, lockValue
  27. else
  28. callback err, false
  29. getLock: (key, callback = (error) ->) ->
  30. startTime = Date.now()
  31. do attempt = () ->
  32. if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME
  33. e = new Error("Timeout")
  34. e.key = key
  35. return callback(e)
  36. LockManager.tryLock key, (error, gotLock, lockValue) ->
  37. return callback(error) if error?
  38. if gotLock
  39. callback(null, lockValue)
  40. else
  41. setTimeout attempt, LockManager.LOCK_TEST_INTERVAL
  42. checkLock: (key, callback = (err, isFree) ->) ->
  43. rclient.exists key, (err, exists) ->
  44. return callback(err) if err?
  45. exists = parseInt exists
  46. if exists == 1
  47. callback err, false
  48. else
  49. callback err, true
  50. releaseLock: (key, lockValue, callback) ->
  51. rclient.eval LockManager.unlockScript, 1, key, lockValue, callback
  52. runWithLock: (key, runner = ( (releaseLock = (error) ->) -> ), callback = ( (error) -> )) ->
  53. LockManager.getLock key, (error, lockValue) ->
  54. return callback(error) if error?
  55. runner (error1) ->
  56. LockManager.releaseLock key, lockValue, (error2) ->
  57. error = error1 or error2
  58. return callback(error) if error?
  59. callback()
  60. healthCheck: (callback) ->
  61. action = (releaseLock) ->
  62. releaseLock()
  63. LockManager.runWithLock "HistoryLock:HealthCheck:host=#{HOST}:pid=#{PID}:random=#{RND}", action, callback
  64. close: (callback) ->
  65. rclient.quit()
  66. rclient.once 'end', callback