LockManager.coffee 2.3 KB

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