DockerLockManager.coffee 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. logger = require "logger-sharelatex"
  2. LockState = {} # locks for docker container operations, by container name
  3. module.exports = LockManager =
  4. MAX_LOCK_HOLD_TIME: 15000 # how long we can keep a lock
  5. MAX_LOCK_WAIT_TIME: 10000 # how long we wait for a lock
  6. LOCK_TEST_INTERVAL: 1000 # retry time
  7. tryLock: (key, callback = (err, gotLock) ->) ->
  8. existingLock = LockState[key]
  9. if existingLock? # the lock is already taken, check how old it is
  10. lockAge = Date.now() - existingLock.created
  11. if lockAge < LockManager.MAX_LOCK_HOLD_TIME
  12. return callback(null, false) # we didn't get the lock, bail out
  13. else
  14. logger.error {key: key, lock: existingLock, age:lockAge}, "taking old lock by force"
  15. # take the lock
  16. LockState[key] = lockValue = {created: Date.now()}
  17. callback(null, true, lockValue)
  18. getLock: (key, callback = (error, lockValue) ->) ->
  19. startTime = Date.now()
  20. do attempt = () ->
  21. LockManager.tryLock key, (error, gotLock, lockValue) ->
  22. return callback(error) if error?
  23. if gotLock
  24. callback(null, lockValue)
  25. else if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME
  26. e = new Error("Lock timeout")
  27. e.key = key
  28. return callback(e)
  29. else
  30. setTimeout attempt, LockManager.LOCK_TEST_INTERVAL
  31. releaseLock: (key, lockValue, callback = (error) ->) ->
  32. existingLock = LockState[key]
  33. if existingLock is lockValue # lockValue is an object, so we can test by reference
  34. delete LockState[key] # our lock, so we can free it
  35. callback()
  36. else if existingLock? # lock exists but doesn't match ours
  37. logger.error {key:key, lock: existingLock}, "tried to release lock taken by force"
  38. callback()
  39. else
  40. logger.error {key:key, lock: existingLock}, "tried to release lock that has gone"
  41. callback()
  42. runWithLock: (key, runner = ( (releaseLock = (error) ->) -> ), callback = ( (error) -> )) ->
  43. LockManager.getLock key, (error, lockValue) ->
  44. return callback(error) if error?
  45. runner (error1, args...) ->
  46. LockManager.releaseLock key, lockValue, (error2) ->
  47. error = error1 or error2
  48. return callback(error) if error?
  49. callback(null, args...)