proper-lockfile
Advanced tools
Comparing version 1.1.3 to 1.2.0
74
index.js
@@ -55,3 +55,3 @@ 'use strict'; | ||
if (stat.mtime.getTime() >= Date.now() - options.stale) { | ||
if (!isLockStale(stat, options)) { | ||
return callback(errcode('Lock file is already being hold', 'ELOCKED', { file: file })); | ||
@@ -73,2 +73,6 @@ } | ||
function isLockStale(stat, options) { | ||
return stat.mtime.getTime() < Date.now() - options.stale; | ||
} | ||
function removeLock(file, options, callback) { | ||
@@ -172,3 +176,3 @@ // Remove lockfile, ignoring ENOENT errors | ||
retries: 0, | ||
fs: fs | ||
fs: fs, | ||
}, options); | ||
@@ -209,3 +213,3 @@ | ||
compromised: compromised, | ||
lastUpdate: Date.now() | ||
lastUpdate: Date.now(), | ||
}; | ||
@@ -237,3 +241,3 @@ | ||
fs: fs, | ||
realpath: true | ||
realpath: true, | ||
}, options); | ||
@@ -266,4 +270,4 @@ | ||
function lockSync(file, options, compromised) { | ||
var err, | ||
release; | ||
var err; | ||
var release; | ||
@@ -312,3 +316,57 @@ if (typeof options === 'function') { | ||
function check(file, options, callback) { | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = null; | ||
} | ||
options = extend({ | ||
stale: 10000, | ||
realpath: true, | ||
fs: fs, | ||
}, options); | ||
options.stale = Math.max(options.stale || 0, 2000); | ||
// Resolve to a canonical file path | ||
canonicalPath(file, options, function (err, file) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
// Check if lockfile exists | ||
options.fs.stat(getLockFile(file), function (err, stat) { | ||
if (err) { | ||
// if does not exist, file is not locked. Otherwise, callback with error | ||
return (err.code === 'ENOENT') ? callback(null, false) : callback(err); | ||
} | ||
if (options.stale <= 0) { return callback(null, true); } | ||
// Otherwise, check if lock is stale by analyzing the file mtime | ||
return callback(null, !isLockStale(stat, options)); | ||
}); | ||
}); | ||
} | ||
function checkSync(file, options) { | ||
var err; | ||
var locked; | ||
options = options || {}; | ||
options.fs = syncFs(options.fs || fs); | ||
check(file, options, function (_err, _locked) { | ||
err = _err; | ||
locked = _locked; | ||
}); | ||
if (err) { | ||
throw err; | ||
} | ||
return locked; | ||
} | ||
// Remove acquired locks on exit | ||
@@ -318,3 +376,3 @@ /* istanbul ignore next */ | ||
Object.keys(locks).forEach(function (file) { | ||
try { locks[file].options.fs.rmdirSync(getLockFile(file)); } catch (e) {} | ||
try { locks[file].options.fs.rmdirSync(getLockFile(file)); } catch (e) { /* empty */ } | ||
}); | ||
@@ -328,1 +386,3 @@ }); | ||
module.exports.unlockSync = unlockSync; | ||
module.exports.check = check; | ||
module.exports.checkSync = checkSync; |
@@ -7,5 +7,5 @@ 'use strict'; | ||
return function () { | ||
var callback = arguments[arguments.length - 1], | ||
args = Array.prototype.slice.call(arguments, 0, -1), | ||
ret; | ||
var callback = arguments[arguments.length - 1]; | ||
var args = Array.prototype.slice.call(arguments, 0, -1); | ||
var ret; | ||
@@ -23,5 +23,5 @@ try { | ||
function syncFs(fs) { | ||
var fns = ['mkdir', 'realpath', 'stat', 'rmdir', 'utimes'], | ||
obj = {}, | ||
key; | ||
var fns = ['mkdir', 'realpath', 'stat', 'rmdir', 'utimes']; | ||
var obj = {}; | ||
var key; | ||
@@ -28,0 +28,0 @@ // Create the sync versions of the methods that we need |
{ | ||
"name": "proper-lockfile", | ||
"version": "1.1.3", | ||
"version": "1.2.0", | ||
"description": "A inter-process and inter-machine lockfile utility that works on a local or network file system.", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint '{*.js,lib/**/*.js,test/**/*.js}' --ignore-pattern=test/coverage", | ||
"test": "mocha --bail", | ||
@@ -37,5 +38,8 @@ "test-cov": "istanbul cover --dir test/coverage _mocha -- --bail && echo open test/coverage/lcov-report/index.html", | ||
"devDependencies": { | ||
"@satazor/eslint-config": "^3.1.1", | ||
"async": "^2.0.0", | ||
"buffered-spawn": "^2.0.4", | ||
"coveralls": "^2.11.6", | ||
"eslint": "^3.5.0", | ||
"eslint-plugin-react": "^6.2.0", | ||
"expect.js": "^0.3.1", | ||
@@ -42,0 +46,0 @@ "istanbul": "^0.4.1", |
@@ -137,3 +137,22 @@ # proper-lockfile | ||
### .check(file, [options], callback) | ||
Check if the file is locked and its lockfile is not stale. Callback is called with callback(error, isLocked). | ||
Available options: | ||
- `stale`: Duration in milliseconds in which the lock is considered stale, defaults to `10000` (minimum value is `5000`) | ||
- `realpath`: Resolve symlinks using realpath, defaults to `true` (note that if `true`, the `file` must exist previously) | ||
- `fs`: A custom fs to use, defaults to `graceful-fs` | ||
```js | ||
var lockfile = require('proper-lockfile'); | ||
lockfile.check('some/file', function (err, isLocked) { | ||
if (err) throw err; | ||
// isLocked will be true if 'some/file' is locked, otherwise will be false if not locked | ||
}); | ||
``` | ||
### .lockSync(file, [options], [compromised]) | ||
@@ -150,3 +169,8 @@ | ||
### .checkSync(file, [options]) | ||
Sync version of `.check()`. | ||
Returns a boolean or throws on error. | ||
## Graceful exit | ||
@@ -153,0 +177,0 @@ |
@@ -19,6 +19,6 @@ 'use strict'; | ||
function master() { | ||
var logs = [], | ||
numCPUs = os.cpus().length, | ||
i, | ||
acquired; | ||
var logs = []; | ||
var numCPUs = os.cpus().length; | ||
var i; | ||
var acquired; | ||
@@ -62,11 +62,14 @@ fs.writeFileSync(file, ''); | ||
return 1; | ||
} else if (log1.timestamp < log2.timestamp) { | ||
} | ||
if (log1.timestamp < log2.timestamp) { | ||
return -1; | ||
} else if (log1.message === 'LOCK_RELEASED') { | ||
} | ||
if (log1.message === 'LOCK_RELEASED') { | ||
return -1; | ||
} else if (log2.message === 'LOCK_RELEASED') { | ||
} | ||
if (log2.message === 'LOCK_RELEASED') { | ||
return 1; | ||
} else { | ||
return 0; | ||
} | ||
return 0; | ||
}); | ||
@@ -86,3 +89,3 @@ | ||
acquired = true; | ||
break; | ||
break; | ||
case 'LOCK_RELEASED': | ||
@@ -96,2 +99,5 @@ if (!acquired) { | ||
acquired = false; | ||
break; | ||
default: | ||
// do nothing | ||
} | ||
@@ -98,0 +104,0 @@ }); |
220
test/test.js
@@ -6,3 +6,3 @@ 'use strict'; | ||
var cp = require('child_process'); | ||
var expect = require('expect.js'); | ||
var expect = require('expect.js'); | ||
var extend = require('extend'); | ||
@@ -306,2 +306,3 @@ var rimraf = require('rimraf'); | ||
var stat = fs.statSync(tmpFileLock); | ||
expect(stat.mtime.getTime()).to.be.greaterThan(mtime.getTime()); | ||
@@ -314,2 +315,3 @@ mtime = stat.mtime; | ||
var stat = fs.statSync(tmpFileLock); | ||
expect(stat.mtime.getTime()).to.be.greaterThan(mtime.getTime()); | ||
@@ -394,3 +396,2 @@ mtime = stat.mtime; | ||
next(); | ||
}, function (err) { | ||
@@ -734,2 +735,167 @@ expect(err).to.not.be.ok(); | ||
describe('.check()', function () { | ||
beforeEach(function () { | ||
fs.writeFileSync(tmpFile, ''); | ||
rimraf.sync(tmpFileSymlink); | ||
}); | ||
afterEach(clearLocks); | ||
this.timeout(5000); | ||
it('should fail if the file does not exist by default', function (next) { | ||
lockfile.check(tmpNonExistentFile, function (err) { | ||
expect(err).to.be.an(Error); | ||
expect(err.code).to.be('ENOENT'); | ||
next(); | ||
}); | ||
}); | ||
it('should not fail if the file does not exist and realpath is false', function (next) { | ||
lockfile.check(tmpNonExistentFile, { realpath: false }, function (err) { | ||
expect(err).to.not.be.ok(); | ||
next(); | ||
}); | ||
}); | ||
it('should callback with true if file is locked', function (next) { | ||
lockfile.lock(tmpFile, function (err) { | ||
expect(err).to.not.be.ok(); | ||
lockfile.check(tmpFile, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(true); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
it('should callback with false if file is not locked', function (next) { | ||
lockfile.check(tmpFile, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(false); | ||
next(); | ||
}); | ||
}); | ||
it('should use the custom fs', function (next) { | ||
var customFs = extend({}, fs); | ||
customFs.realpath = function (path, callback) { | ||
customFs.realpath = fs.realpath; | ||
callback(new Error('foo')); | ||
}; | ||
lockfile.check(tmpFile, { fs: customFs }, function (err, locked) { | ||
expect(err).to.be.an(Error); | ||
expect(locked).to.be(undefined); | ||
next(); | ||
}); | ||
}); | ||
it('should resolve symlinks by default', function (next) { | ||
// Create a symlink to the tmp file | ||
fs.symlinkSync(tmpFileRealPath, tmpFileSymlinkRealPath); | ||
lockfile.lock(tmpFileSymlink, function (err) { | ||
expect(err).to.not.be.ok(); | ||
lockfile.check(tmpFile, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(true); | ||
lockfile.check(tmpFile + '/../../test/tmp', function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(true); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should not resolve symlinks if realpath is false', function (next) { | ||
// Create a symlink to the tmp file | ||
fs.symlinkSync(tmpFileRealPath, tmpFileSymlinkRealPath); | ||
lockfile.lock(tmpFileSymlink, { realpath: false }, function (err) { | ||
expect(err).to.not.be.ok(); | ||
lockfile.check(tmpFile, { realpath: false }, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(false); | ||
lockfile.check(tmpFile + '/../../test/tmp', { realpath: false }, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.be(false); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should fail if stating the lockfile errors out when verifying staleness', function (next) { | ||
var mtime = (Date.now() - 60000) / 1000; | ||
var customFs = extend({}, fs); | ||
customFs.stat = function (path, callback) { | ||
callback(new Error('foo')); | ||
}; | ||
fs.mkdirSync(tmpFileLock); | ||
fs.utimesSync(tmpFileLock, mtime, mtime); | ||
lockfile.check(tmpFile, { fs: customFs }, function (err, locked) { | ||
expect(err).to.be.an(Error); | ||
expect(err.message).to.be('foo'); | ||
expect(locked).to.be(undefined); | ||
next(); | ||
}); | ||
}); | ||
it('should set stale to a minimum of 2000', function (next) { | ||
fs.mkdirSync(tmpFileLock); | ||
setTimeout(function () { | ||
lockfile.lock(tmpFile, { stale: 2000 }, function (err) { | ||
expect(err).to.be.an(Error); | ||
expect(err.code).to.be('ELOCKED'); | ||
}); | ||
}, 200); | ||
setTimeout(function () { | ||
lockfile.check(tmpFile, { stale: 100 }, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.equal(false); | ||
next(); | ||
}); | ||
}, 2200); | ||
}); | ||
it('should set stale to a minimum of 2000 (falsy)', function (next) { | ||
fs.mkdirSync(tmpFileLock); | ||
setTimeout(function () { | ||
lockfile.lock(tmpFile, { stale: 2000 }, function (err) { | ||
expect(err).to.be.an(Error); | ||
expect(err.code).to.be('ELOCKED'); | ||
}); | ||
}, 200); | ||
setTimeout(function () { | ||
lockfile.check(tmpFile, { stale: false }, function (err, locked) { | ||
expect(err).to.not.be.ok(); | ||
expect(locked).to.equal(false); | ||
next(); | ||
}); | ||
}, 2200); | ||
}); | ||
}); | ||
describe('release()', function () { | ||
@@ -833,2 +999,3 @@ beforeEach(function () { | ||
var release = lockfile.lockSync(tmpFile, { retries: 0 }); | ||
release(); | ||
@@ -839,2 +1006,3 @@ }).to.not.throwException(); | ||
var release = lockfile.lockSync(tmpFile, { retries: { retries: 0 } }); | ||
release(); | ||
@@ -869,2 +1037,3 @@ }).to.not.throwException(); | ||
var stat = fs.statSync(tmpFileLock); | ||
expect(stat.mtime.getTime()).to.be.greaterThan(mtime.getTime()); | ||
@@ -877,2 +1046,3 @@ mtime = stat.mtime; | ||
var stat = fs.statSync(tmpFileLock); | ||
expect(stat.mtime.getTime()).to.be.greaterThan(mtime.getTime()); | ||
@@ -886,4 +1056,4 @@ mtime = stat.mtime; | ||
it('should use a custom fs', function () { | ||
var customFs = extend({}, fs), | ||
called; | ||
var customFs = extend({}, fs); | ||
var called; | ||
@@ -898,2 +1068,44 @@ customFs.realpathSync = function () { | ||
}); | ||
it('should expose a working checkSync', function () { | ||
var release; | ||
var locked; | ||
// Test success unlocked | ||
locked = lockfile.checkSync(tmpFile); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(false); | ||
// Test success locked | ||
release = lockfile.lockSync(tmpFile); | ||
locked = lockfile.checkSync(tmpFile); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(true); | ||
// Test success unlocked after release | ||
release(); | ||
locked = lockfile.checkSync(tmpFile); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(false); | ||
// Test options being passed | ||
locked = lockfile.checkSync(tmpFile, {}); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(false); | ||
release = lockfile.lockSync(tmpFile); | ||
locked = lockfile.checkSync(tmpFile, {}); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(true); | ||
release(); | ||
locked = lockfile.checkSync(tmpFile, {}); | ||
expect(locked).to.be.a('boolean'); | ||
expect(locked).to.be(false); | ||
// Test fail with non-existent file | ||
expect(function () { | ||
lockfile.checkSync('nonexistentdir/nonexistentfile'); | ||
}).to.throwException(/ENOENT/); | ||
}); | ||
}); | ||
@@ -900,0 +1112,0 @@ |
61945
14
1358
204
11