Socket
Socket
Sign inDemoInstall

proper-lockfile

Package Overview
Dependencies
2
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.1 to 3.0.0

CHANGELOG.md

369

index.js
'use strict';
const fs = require('graceful-fs');
const path = require('path');
const retry = require('retry');
const syncFs = require('./lib/syncFs');
const lockfile = require('./lib/lockfile');
const { toPromise, toSync, toSyncOptions } = require('./lib/adapter');
const locks = {};
async function lock(file, options) {
const release = await toPromise(lockfile.lock)(file, options);
function getLockFile(file) {
return `${file}.lock`;
return toPromise(release);
}
function canonicalPath(file, options, callback) {
if (!options.realpath) {
return callback(null, path.resolve(file));
}
function lockSync(file, options) {
const release = toSync(lockfile.lock)(file, toSyncOptions(options));
// Use realpath to resolve symlinks
// It also resolves relative paths
options.fs.realpath(file, callback);
return toSync(release);
}
function acquireLock(file, options, callback) {
// Use mkdir to create the lockfile (atomic operation)
options.fs.mkdir(getLockFile(file), (err) => {
// If successful, we are done
if (!err) {
return callback();
}
// If error is not EEXIST then some other error occurred while locking
if (err.code !== 'EEXIST') {
return callback(err);
}
// Otherwise, check if lock is stale by analyzing the file mtime
if (options.stale <= 0) {
return callback(Object.assign(new Error('Lock file is already being hold'), { code: 'ELOCKED', file }));
}
options.fs.stat(getLockFile(file), (err, stat) => {
if (err) {
// Retry if the lockfile has been removed (meanwhile)
// Skip stale check to avoid recursiveness
if (err.code === 'ENOENT') {
return acquireLock(file, Object.assign({}, options, { stale: 0 }), callback);
}
return callback(err);
}
if (!isLockStale(stat, options)) {
return callback(Object.assign(new Error('Lock file is already being hold'), { code: 'ELOCKED', file }));
}
// If it's stale, remove it and try again!
// Skip stale check to avoid recursiveness
removeLock(file, options, (err) => {
if (err) {
return callback(err);
}
acquireLock(file, Object.assign({}, options, { stale: 0 }), callback);
});
});
});
function unlock(file, options) {
return toPromise(lockfile.unlock)(file, options);
}
function isLockStale(stat, options) {
return stat.mtime.getTime() < Date.now() - options.stale;
}
function removeLock(file, options, callback) {
// Remove lockfile, ignoring ENOENT errors
options.fs.rmdir(getLockFile(file), (err) => {
if (err && err.code !== 'ENOENT') {
return callback(err);
}
callback();
});
}
function updateLock(file, options) {
const lock = locks[file];
/* istanbul ignore next */
if (lock.updateTimeout) {
return;
}
lock.updateDelay = lock.updateDelay || options.update;
lock.updateTimeout = setTimeout(() => {
const mtime = Date.now() / 1000;
lock.updateTimeout = null;
options.fs.utimes(getLockFile(file), mtime, mtime, (err) => {
// Ignore if the lock was released
if (lock.released) {
return;
}
// Verify if we are within the stale threshold
if (lock.lastUpdate <= Date.now() - options.stale &&
lock.lastUpdate > Date.now() - options.stale * 2) {
return compromisedLock(file, lock, Object.assign(new Error(lock.updateError || 'Unable to update lock within the stale \
threshold'), { code: 'ECOMPROMISED' }));
}
// If the file is older than (stale * 2), we assume the clock is moved manually,
// which we consider a valid case
// If it failed to update the lockfile, keep trying unless
// the lockfile was deleted!
if (err) {
if (err.code === 'ENOENT') {
return compromisedLock(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
}
lock.updateError = err;
lock.updateDelay = 1000;
return updateLock(file, options);
}
// All ok, keep updating..
lock.lastUpdate = Date.now();
lock.updateError = null;
lock.updateDelay = null;
updateLock(file, options);
});
}, lock.updateDelay);
// Unref the timer so that the nodejs process can exit freely
// This is safe because all acquired locks will be automatically released
// on process exit
// We first check that `lock.updateTimeout.unref` exists because some users
// may be using this module outside of NodeJS (e.g., in an electron app),
// and in those cases `setTimeout` return an integer.
if (lock.updateTimeout.unref) {
lock.updateTimeout.unref();
}
}
function compromisedLock(file, lock, err) {
lock.released = true; // Signal the lock has been released
/* istanbul ignore next */
lock.updateTimeout && clearTimeout(lock.updateTimeout); // Cancel lock mtime update
if (locks[file] === lock) {
delete locks[file];
}
lock.compromised(err);
}
// -----------------------------------------
function lock(file, options, compromised, callback) {
if (typeof options === 'function') {
callback = compromised;
compromised = options;
options = null;
}
if (!callback) {
callback = compromised;
compromised = null;
}
options = Object.assign({
stale: 10000,
update: null,
realpath: true,
retries: 0,
fs,
}, options);
options.retries = options.retries || 0;
options.retries = typeof options.retries === 'number' ? { retries: options.retries } : options.retries;
options.stale = Math.max(options.stale || 0, 2000);
options.update = options.update == null ? options.stale / 2 : options.update || 0;
options.update = Math.max(Math.min(options.update, options.stale / 2), 1000);
compromised = compromised || function (err) { throw err; };
// Resolve to a canonical file path
canonicalPath(file, options, (err, file) => {
if (err) {
return callback(err);
}
// Attempt to acquire the lock
const operation = retry.operation(options.retries);
operation.attempt(() => {
acquireLock(file, options, (err) => {
if (operation.retry(err)) {
return;
}
if (err) {
return callback(operation.mainError());
}
// We now own the lock
const lock = locks[file] = {
options,
compromised,
lastUpdate: Date.now(),
};
// We must keep the lock fresh to avoid staleness
updateLock(file, options);
callback(null, (releasedCallback) => {
if (lock.released) {
return releasedCallback &&
releasedCallback(Object.assign(new Error('Lock is already released'), { code: 'ERELEASED' }));
}
// Not necessary to use realpath twice when unlocking
unlock(file, Object.assign({}, options, { realpath: false }), releasedCallback);
});
});
});
});
}
function unlock(file, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = Object.assign({
fs,
realpath: true,
}, options);
callback = callback || function () {};
// Resolve to a canonical file path
canonicalPath(file, options, (err, file) => {
if (err) {
return callback(err);
}
// Skip if the lock is not acquired
const lock = locks[file];
if (!lock) {
return callback(Object.assign(new Error('Lock is not acquired/owned by you'), { code: 'ENOTACQUIRED' }));
}
lock.updateTimeout && clearTimeout(lock.updateTimeout); // Cancel lock mtime update
lock.released = true; // Signal the lock has been released
delete locks[file]; // Delete from locks
removeLock(file, options, callback);
});
}
function lockSync(file, options, compromised) {
if (typeof options === 'function') {
compromised = options;
options = null;
}
options = options || {};
options.fs = syncFs(options.fs || fs);
options.retries = options.retries || 0;
options.retries = typeof options.retries === 'number' ? { retries: options.retries } : options.retries;
// Retries are not allowed because it requires the flow to be sync
if (options.retries.retries) {
throw Object.assign(new Error('Cannot use retries with the sync api'), { code: 'ESYNC' });
}
let err;
let release;
lock(file, options, compromised, (_err, _release) => {
err = _err;
release = _release;
});
if (err) {
throw err;
}
return release;
}
function unlockSync(file, options) {
options = options || {};
options.fs = syncFs(options.fs || fs);
let err;
unlock(file, options, (_err) => {
err = _err;
});
if (err) {
throw err;
}
return toSync(lockfile.unlock)(file, toSyncOptions(options));
}
function check(file, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = Object.assign({
stale: 10000,
realpath: true,
fs,
}, options);
options.stale = Math.max(options.stale || 0, 2000);
// Resolve to a canonical file path
canonicalPath(file, options, (err, file) => {
if (err) {
return callback(err);
}
// Check if lockfile exists
options.fs.stat(getLockFile(file), (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 check(file, options) {
return toPromise(lockfile.check)(file, options);
}
function checkSync(file, options) {
options = options || {};
options.fs = syncFs(options.fs || fs);
let err;
let locked;
check(file, options, (_err, _locked) => {
err = _err;
locked = _locked;
});
if (err) {
throw err;
}
return locked;
return toSync(lockfile.check)(file, toSyncOptions(options));
}
// Remove acquired locks on exit
/* istanbul ignore next */
process.on('exit', () => {
Object.keys(locks).forEach((file) => {
try { locks[file].options.fs.rmdirSync(getLockFile(file)); } catch (e) { /* empty */ }
});
});
module.exports = lock;
module.exports.lock = lock;

@@ -376,0 +35,0 @@ module.exports.unlock = unlock;

{
"name": "proper-lockfile",
"version": "2.0.1",
"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",
"test-cov": "istanbul cover --dir test/coverage _mocha && echo open test/coverage/lcov-report/index.html",
"test-travis": "istanbul cover _mocha --report lcovonly && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
},
"bugs": {
"url": "https://github.com/IndigoUnited/node-proper-lockfile/issues/"
},
"repository": {
"type": "git",
"url": "git://github.com/IndigoUnited/node-proper-lockfile.git"
},
"version": "3.0.0",
"description": "A inter-process and inter-machine lockfile utility that works on a local or network file system",
"keywords": [

@@ -25,28 +11,59 @@ "lock",

"fs",
"rename",
"cross",
"machine"
"cross-process"
],
"author": "IndigoUnited <hello@indigounited.com> (http://indigounited.com)",
"author": "André Cruz <andre@moxy.studio>",
"homepage": "https://github.com/moxystudio/node-promptly",
"repository": {
"type": "git",
"url": "git@github.com:moxystudio/node-promptly.git"
},
"license": "MIT",
"main": "index.js",
"files": [
"lib"
],
"scripts": {
"lint": "eslint .",
"test": "jest --env node --coverage --runInBand",
"prerelease": "npm t && npm run lint",
"release": "standard-version",
"precommit": "lint-staged",
"commitmsg": "commitlint -e $GIT_PARAMS"
},
"standard-version": {
"scripts": {
"posttag": "git push --follow-tags origin master && npm publish"
}
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"dependencies": {
"graceful-fs": "^4.1.2",
"retry": "^0.10.0"
"graceful-fs": "^4.1.11",
"retry": "^0.10.1"
},
"devDependencies": {
"@satazor/eslint-config": "^3.1.1",
"async": "^2.0.0",
"buffered-spawn": "^3.0.0",
"coveralls": "^2.11.6",
"eslint": "^3.5.0",
"eslint-plugin-react": "^6.2.0",
"expect.js": "^0.3.1",
"istanbul": "^0.4.1",
"mocha": "^3.0.2",
"rimraf": "^2.5.0",
"stable": "^0.1.5"
},
"engines": {
"node": ">=4.0.0"
"@commitlint/cli": "^6.0.0",
"@commitlint/config-conventional": "^6.0.2",
"@segment/clear-timeouts": "^2.0.0",
"delay": "^2.0.0",
"eslint": "^4.3.0",
"eslint-config-moxy": "^4.1.0",
"husky": "^0.14.3",
"jest": "^22.0.0",
"lint-staged": "^6.0.0",
"mkdirp": "^0.5.1",
"p-defer": "^1.0.0",
"rimraf": "^2.6.2",
"stable": "^0.1.6",
"standard-version": "^4.2.0"
}
}
# proper-lockfile
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url]
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][codecov-image]][codecov-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url]

@@ -8,10 +8,12 @@ [npm-url]:https://npmjs.org/package/proper-lockfile

[npm-image]:http://img.shields.io/npm/v/proper-lockfile.svg
[travis-url]:https://travis-ci.org/IndigoUnited/node-proper-lockfile
[travis-image]:http://img.shields.io/travis/IndigoUnited/node-proper-lockfile/master.svg
[coveralls-url]:https://coveralls.io/r/IndigoUnited/node-proper-lockfile
[coveralls-image]:https://img.shields.io/coveralls/IndigoUnited/node-proper-lockfile/master.svg
[david-dm-url]:https://david-dm.org/IndigoUnited/node-proper-lockfile
[david-dm-image]:https://img.shields.io/david/IndigoUnited/node-proper-lockfile.svg
[david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-proper-lockfile#info=devDependencies
[david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-proper-lockfile.svg
[travis-url]:https://travis-ci.org/moxystudio/node-proper-lockfile
[travis-image]:http://img.shields.io/travis/moxystudio/node-proper-lockfile/master.svg
[codecov-url]:https://codecov.io/gh/moxystudio/node-proper-lockfile
[codecov-image]:https://img.shields.io/codecov/c/github/moxystudio/node-proper-lockfile/master.svg
[david-dm-url]:https://david-dm.org/moxystudio/node-proper-lockfile
[david-dm-image]:https://img.shields.io/david/moxystudio/node-proper-lockfile.svg
[david-dm-dev-url]:https://david-dm.org/moxystudio/node-proper-lockfile?type=dev
[david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/node-proper-lockfile.svg
[greenkeeper-image]:https://badges.greenkeeper.io/moxystudio/node-proper-lockfile.svg
[greenkeeper-url]:https://greenkeeper.io/

@@ -67,8 +69,7 @@ An inter-process and inter-machine lockfile utility that works on a local or network file system.

### .lock(file, [options], [compromised], callback)
### .lock(file, [options])
Tries to acquire a lock on `file`.
If the lock succeeds, a `release` function is provided that should be called when you want to release the lock.
If the lock gets compromised, the `compromised` function will be called. The default `compromised` function is a simple `throw err` which will probably cause the process to die. Specify it to handle the way you desire.
If the lock succeeds, a `release` function is provided that should be called when you want to release the lock.

@@ -82,2 +83,3 @@ Available options:

- `fs`: A custom fs to use, defaults to `graceful-fs`
- `onCompromised`: Called if the lock gets compromised, defaults to a function that simply throws the error which will probably cause the process to die

@@ -88,21 +90,9 @@

lockfile.lock('some/file', (err, release) => {
if (err) {
throw err; // Lock failed
}
lockfile.lock('some/file')
.then((release) => {
// Do something while the file is locked
// Call the provided release function when you're done
release();
// Note that you can optionally handle release errors
// Though it's not mandatory since it will eventually stale
/*release((err) => {
// At this point the lock was effectively released or an error
// occurred while removing it
if (err) {
throw err;
}
});*/
// Call the provided release function when you're done,
// which will also return a promise
return release();
});

@@ -112,3 +102,3 @@ ```

### .unlock(file, [options], [callback])
### .unlock(file, [options])

@@ -119,4 +109,2 @@ Releases a previously acquired lock on `file`.

The `callback` is optional because even if the removal of the lock failed, the lockfile's `mtime` will no longer be updated causing it to eventually stale.
Available options:

@@ -131,24 +119,14 @@

lockfile.lock('some/file', (err) => {
if (err) {
throw err;
}
lockfile.lock('some/file')
.then(() => {
// Do something while the file is locked
// Later..
lockfile.unlock('some/file');
// or..
/*lockfile.unlock('some/file', (err) => {
// At this point the lock was effectively released or an error
// occurred while removing it
if (err) {
throw err;
}
});*/
return lockfile.unlock('some/file');
});
```
### .check(file, [options], callback)
### .check(file, [options])
Check if the file is locked and its lockfile is not stale. Callback is called with callback(error, isLocked).
Check if the file is locked and its lockfile is not stale.

@@ -165,7 +143,4 @@ Available options:

lockfile.check('some/file', (err, isLocked) => {
if (err) {
throw err;
}
lockfile.check('some/file')
.then((isLocked) => {
// isLocked will be true if 'some/file' is locked, false otherwise

@@ -175,3 +150,3 @@ });

### .lockSync(file, [options], [compromised])
### .lockSync(file, [options])

@@ -181,3 +156,2 @@ Sync version of `.lock()`.

### .unlockSync(file, [options])

@@ -213,3 +187,3 @@

`$ npm test`
`$ npm test-cov` to get coverage report
`$ npm test -- --watch` during development

@@ -216,0 +190,0 @@ The test suite is very extensive. There's even a stress test to guarantee exclusiveness of locks.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc