updateLock: implement a module for a file-based update lock

This update lock library allows an application to take a lockfile in several locations (subdirectories inside a base folder). The user of this library must be able
to exclusively create a lockfile in each of the corresponding locations, and if any of the files exist, the locking fails.

Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2017-11-01 00:58:01 -07:00
parent cb37f7ebcc
commit dac153eb8c

View File

@ -0,0 +1,51 @@
Promise = require 'bluebird'
_ = require 'lodash'
TypedError = require 'typed-error'
lockFile = Promise.promisifyAll(require('lockfile'))
Lock = require 'rwlock'
fs = Promise.promisifyAll(require('fs'))
constants = require './constants'
ENOENT = (err) -> err.code is 'ENOENT'
baseLockPath = (appId) ->
return "/tmp/resin-supervisor/services/#{appId}"
exports.lockPath = (appId, serviceName) ->
return "#{baseLockPath(appId)}/#{serviceName}"
lockFileOnHost = (appId, serviceName) ->
return "#{constants.rootMountPoint}#{exports.lockPath(appId, serviceName)}/resin-updates.lock"
exports.UpdatesLockedError = class UpdatesLockedError extends TypedError
exports.lock = do ->
_lock = new Lock()
_writeLock = Promise.promisify(_lock.async.writeLock)
return (appId, { force = false } = {}) ->
Promise.try ->
return if !appId?
locksTaken = []
dispose = (release) ->
Promise.map locksTaken, (lockName) ->
lockFile.unlockAsync(lockName)
.finally ->
release()
_writeLock(appId)
.tap (release) ->
fs.readdirAsync(baseLockPath(appId))
.catch ENOENT, -> []
.mapSeries (serviceName) ->
tmpLockName = lockFileOnHost(appId, serviceName)
Promise.try ->
lockFile.unlockAsync(tmpLockName) if force == true
.then ->
lockFile.lockAsync(tmpLockName)
.then ->
locksTaken.push(tmpLockName)
.catch ENOENT, _.noop
.catch (err) ->
dispose(release)
.finally ->
throw new exports.UpdatesLockedError("Updates are locked: #{err.message}")
.disposer(dispose)