taskchef 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/node_modules/graceful-fs/LICENSE +15 -0
  3. package/node_modules/graceful-fs/README.md +143 -0
  4. package/node_modules/graceful-fs/clone.js +23 -0
  5. package/node_modules/graceful-fs/graceful-fs.js +448 -0
  6. package/node_modules/graceful-fs/legacy-streams.js +118 -0
  7. package/node_modules/graceful-fs/package.json +53 -0
  8. package/node_modules/graceful-fs/polyfills.js +355 -0
  9. package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
  10. package/node_modules/proper-lockfile/LICENSE +21 -0
  11. package/node_modules/proper-lockfile/README.md +183 -0
  12. package/node_modules/proper-lockfile/index.js +40 -0
  13. package/node_modules/proper-lockfile/lib/adapter.js +85 -0
  14. package/node_modules/proper-lockfile/lib/lockfile.js +342 -0
  15. package/node_modules/proper-lockfile/lib/mtime-precision.js +55 -0
  16. package/node_modules/proper-lockfile/package.json +71 -0
  17. package/node_modules/retry/.npmignore +3 -0
  18. package/node_modules/retry/.travis.yml +15 -0
  19. package/node_modules/retry/License +21 -0
  20. package/node_modules/retry/Makefile +18 -0
  21. package/node_modules/retry/README.md +227 -0
  22. package/node_modules/retry/equation.gif +0 -0
  23. package/node_modules/retry/example/dns.js +31 -0
  24. package/node_modules/retry/example/stop.js +40 -0
  25. package/node_modules/retry/index.js +1 -0
  26. package/node_modules/retry/lib/retry.js +100 -0
  27. package/node_modules/retry/lib/retry_operation.js +158 -0
  28. package/node_modules/retry/package.json +32 -0
  29. package/node_modules/retry/test/common.js +10 -0
  30. package/node_modules/retry/test/integration/test-forever.js +24 -0
  31. package/node_modules/retry/test/integration/test-retry-operation.js +258 -0
  32. package/node_modules/retry/test/integration/test-retry-wrap.js +101 -0
  33. package/node_modules/retry/test/integration/test-timeouts.js +69 -0
  34. package/node_modules/signal-exit/LICENSE.txt +16 -0
  35. package/node_modules/signal-exit/README.md +39 -0
  36. package/node_modules/signal-exit/index.js +202 -0
  37. package/node_modules/signal-exit/package.json +38 -0
  38. package/node_modules/signal-exit/signals.js +53 -0
  39. package/package.json +5 -2
@@ -0,0 +1,183 @@
1
+ # proper-lockfile
2
+
3
+ [![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]
4
+
5
+ [npm-url]:https://npmjs.org/package/proper-lockfile
6
+ [downloads-image]:https://img.shields.io/npm/dm/proper-lockfile.svg
7
+ [npm-image]:https://img.shields.io/npm/v/proper-lockfile.svg
8
+ [travis-url]:https://travis-ci.org/moxystudio/node-proper-lockfile
9
+ [travis-image]:https://img.shields.io/travis/moxystudio/node-proper-lockfile/master.svg
10
+ [codecov-url]:https://codecov.io/gh/moxystudio/node-proper-lockfile
11
+ [codecov-image]:https://img.shields.io/codecov/c/github/moxystudio/node-proper-lockfile/master.svg
12
+ [david-dm-url]:https://david-dm.org/moxystudio/node-proper-lockfile
13
+ [david-dm-image]:https://img.shields.io/david/moxystudio/node-proper-lockfile.svg
14
+ [david-dm-dev-url]:https://david-dm.org/moxystudio/node-proper-lockfile?type=dev
15
+ [david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/node-proper-lockfile.svg
16
+
17
+ An inter-process and inter-machine lockfile utility that works on a local or network file system.
18
+
19
+
20
+ ## Installation
21
+
22
+ `$ npm install proper-lockfile`
23
+
24
+
25
+ ## Design
26
+
27
+ There are various ways to achieve [file locking](http://en.wikipedia.org/wiki/File_locking).
28
+
29
+ This library utilizes the `mkdir` strategy which works atomically on any kind of file system, even network based ones.
30
+ The lockfile path is based on the file path you are trying to lock by suffixing it with `.lock`.
31
+
32
+ When a lock is successfully acquired, the lockfile's `mtime` (modified time) is periodically updated to prevent staleness. This allows to effectively check if a lock is stale by checking its `mtime` against a stale threshold. If the update of the mtime fails several times, the lock might be compromised. The `mtime` is [supported](http://en.wikipedia.org/wiki/Comparison_of_file_systems) in almost every `filesystem`.
33
+
34
+
35
+ ### Comparison
36
+
37
+ This library is similar to [lockfile](https://github.com/isaacs/lockfile) but the latter has some drawbacks:
38
+
39
+ - It relies on `open` with `O_EXCL` flag which has problems in network file systems. `proper-lockfile` uses `mkdir` which doesn't have this issue.
40
+
41
+ > O_EXCL is broken on NFS file systems; programs which rely on it for performing locking tasks will contain a race condition.
42
+
43
+ - The lockfile staleness check is done via `ctime` (creation time) which is unsuitable for long running processes. `proper-lockfile` constantly updates lockfiles `mtime` to do proper staleness check.
44
+
45
+ - It does not check if the lockfile was compromised which can lead to undesirable situations. `proper-lockfile` checks the lockfile when updating the `mtime`.
46
+
47
+ - It has a default value of `0` for the stale option which isn't good because any crash or process kill that the package can't handle gracefully will leave the lock active forever.
48
+
49
+
50
+ ### Compromised
51
+
52
+ `proper-lockfile` does not detect cases in which:
53
+
54
+ - A `lockfile` is manually removed and someone else acquires the lock right after
55
+ - Different `stale`/`update` values are being used for the same file, possibly causing two locks to be acquired on the same file
56
+
57
+ `proper-lockfile` detects cases in which:
58
+
59
+ - Updates to the `lockfile` fail
60
+ - Updates take longer than expected, possibly causing the lock to become stale for a certain amount of time
61
+
62
+
63
+ As you see, the first two are a consequence of bad usage. Technically, it was possible to detect the first two but it would introduce complexity and eventual race conditions.
64
+
65
+
66
+ ## Usage
67
+
68
+ ### .lock(file, [options])
69
+
70
+ Tries to acquire a lock on `file` or rejects the promise on error.
71
+
72
+ If the lock succeeds, a `release` function is provided that should be called when you want to release the lock. The `release` function also rejects the promise on error (e.g. when the lock was already compromised).
73
+
74
+ Available options:
75
+
76
+ - `stale`: Duration in milliseconds in which the lock is considered stale, defaults to `10000` (minimum value is `5000`)
77
+ - `update`: The interval in milliseconds in which the lockfile's `mtime` will be updated, defaults to `stale/2` (minimum value is `1000`, maximum value is `stale/2`)
78
+ - `retries`: The number of retries or a [retry](https://www.npmjs.org/package/retry) options object, defaults to `0`
79
+ - `realpath`: Resolve symlinks using realpath, defaults to `true` (note that if `true`, the `file` must exist previously)
80
+ - `fs`: A custom fs to use, defaults to `graceful-fs`
81
+ - `onCompromised`: Called if the lock gets compromised, defaults to a function that simply throws the error which will probably cause the process to die
82
+ - `lockfilePath`: Custom lockfile path. e.g.: If you want to lock a directory and create the lock file inside it, you can pass `file` as `<dir path>` and `options.lockfilePath` as `<dir path>/dir.lock`
83
+
84
+
85
+ ```js
86
+ const lockfile = require('proper-lockfile');
87
+
88
+ lockfile.lock('some/file')
89
+ .then((release) => {
90
+ // Do something while the file is locked
91
+
92
+ // Call the provided release function when you're done,
93
+ // which will also return a promise
94
+ return release();
95
+ })
96
+ .catch((e) => {
97
+ // either lock could not be acquired
98
+ // or releasing it failed
99
+ console.error(e)
100
+ });
101
+
102
+ // Alternatively, you may use lockfile('some/file') directly.
103
+ ```
104
+
105
+
106
+ ### .unlock(file, [options])
107
+
108
+ Releases a previously acquired lock on `file` or rejects the promise on error.
109
+
110
+ Whenever possible you should use the `release` function instead (as exemplified above). Still there are cases in which it's hard to keep a reference to it around code. In those cases `unlock()` might be handy.
111
+
112
+ Available options:
113
+
114
+ - `realpath`: Resolve symlinks using realpath, defaults to `true` (note that if `true`, the `file` must exist previously)
115
+ - `fs`: A custom fs to use, defaults to `graceful-fs`
116
+ - `lockfilePath`: Custom lockfile path. e.g.: If you want to lock a directory and create the lock file inside it, you can pass `file` as `<dir path>` and `options.lockfilePath` as `<dir path>/dir.lock`
117
+
118
+
119
+ ```js
120
+ const lockfile = require('proper-lockfile');
121
+
122
+ lockfile.lock('some/file')
123
+ .then(() => {
124
+ // Do something while the file is locked
125
+
126
+ // Later..
127
+ return lockfile.unlock('some/file');
128
+ });
129
+ ```
130
+
131
+ ### .check(file, [options])
132
+
133
+ Check if the file is locked and its lockfile is not stale, rejects the promise on error.
134
+
135
+ Available options:
136
+
137
+ - `stale`: Duration in milliseconds in which the lock is considered stale, defaults to `10000` (minimum value is `5000`)
138
+ - `realpath`: Resolve symlinks using realpath, defaults to `true` (note that if `true`, the `file` must exist previously)
139
+ - `fs`: A custom fs to use, defaults to `graceful-fs`
140
+ - `lockfilePath`: Custom lockfile path. e.g.: If you want to lock a directory and create the lock file inside it, you can pass `file` as `<dir path>` and `options.lockfilePath` as `<dir path>/dir.lock`
141
+
142
+
143
+ ```js
144
+ const lockfile = require('proper-lockfile');
145
+
146
+ lockfile.check('some/file')
147
+ .then((isLocked) => {
148
+ // isLocked will be true if 'some/file' is locked, false otherwise
149
+ });
150
+ ```
151
+
152
+ ### .lockSync(file, [options])
153
+
154
+ Sync version of `.lock()`.
155
+ Returns the `release` function or throws on error.
156
+
157
+ ### .unlockSync(file, [options])
158
+
159
+ Sync version of `.unlock()`.
160
+ Throws on error.
161
+
162
+ ### .checkSync(file, [options])
163
+
164
+ Sync version of `.check()`.
165
+ Returns a boolean or throws on error.
166
+
167
+
168
+ ## Graceful exit
169
+
170
+ `proper-lockfile` automatically removes locks if the process exits, except if the process is killed with SIGKILL or it crashes due to a VM fatal error (e.g.: out of memory).
171
+
172
+
173
+ ## Tests
174
+
175
+ `$ npm test`
176
+ `$ npm test -- --watch` during development
177
+
178
+ The test suite is very extensive. There's even a stress test to guarantee exclusiveness of locks.
179
+
180
+
181
+ ## License
182
+
183
+ Released under the [MIT License](https://www.opensource.org/licenses/mit-license.php).
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const lockfile = require('./lib/lockfile');
4
+ const { toPromise, toSync, toSyncOptions } = require('./lib/adapter');
5
+
6
+ async function lock(file, options) {
7
+ const release = await toPromise(lockfile.lock)(file, options);
8
+
9
+ return toPromise(release);
10
+ }
11
+
12
+ function lockSync(file, options) {
13
+ const release = toSync(lockfile.lock)(file, toSyncOptions(options));
14
+
15
+ return toSync(release);
16
+ }
17
+
18
+ function unlock(file, options) {
19
+ return toPromise(lockfile.unlock)(file, options);
20
+ }
21
+
22
+ function unlockSync(file, options) {
23
+ return toSync(lockfile.unlock)(file, toSyncOptions(options));
24
+ }
25
+
26
+ function check(file, options) {
27
+ return toPromise(lockfile.check)(file, options);
28
+ }
29
+
30
+ function checkSync(file, options) {
31
+ return toSync(lockfile.check)(file, toSyncOptions(options));
32
+ }
33
+
34
+ module.exports = lock;
35
+ module.exports.lock = lock;
36
+ module.exports.unlock = unlock;
37
+ module.exports.lockSync = lockSync;
38
+ module.exports.unlockSync = unlockSync;
39
+ module.exports.check = check;
40
+ module.exports.checkSync = checkSync;
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const fs = require('graceful-fs');
4
+
5
+ function createSyncFs(fs) {
6
+ const methods = ['mkdir', 'realpath', 'stat', 'rmdir', 'utimes'];
7
+ const newFs = { ...fs };
8
+
9
+ methods.forEach((method) => {
10
+ newFs[method] = (...args) => {
11
+ const callback = args.pop();
12
+ let ret;
13
+
14
+ try {
15
+ ret = fs[`${method}Sync`](...args);
16
+ } catch (err) {
17
+ return callback(err);
18
+ }
19
+
20
+ callback(null, ret);
21
+ };
22
+ });
23
+
24
+ return newFs;
25
+ }
26
+
27
+ // ----------------------------------------------------------
28
+
29
+ function toPromise(method) {
30
+ return (...args) => new Promise((resolve, reject) => {
31
+ args.push((err, result) => {
32
+ if (err) {
33
+ reject(err);
34
+ } else {
35
+ resolve(result);
36
+ }
37
+ });
38
+
39
+ method(...args);
40
+ });
41
+ }
42
+
43
+ function toSync(method) {
44
+ return (...args) => {
45
+ let err;
46
+ let result;
47
+
48
+ args.push((_err, _result) => {
49
+ err = _err;
50
+ result = _result;
51
+ });
52
+
53
+ method(...args);
54
+
55
+ if (err) {
56
+ throw err;
57
+ }
58
+
59
+ return result;
60
+ };
61
+ }
62
+
63
+ function toSyncOptions(options) {
64
+ // Shallow clone options because we are oging to mutate them
65
+ options = { ...options };
66
+
67
+ // Transform fs to use the sync methods instead
68
+ options.fs = createSyncFs(options.fs || fs);
69
+
70
+ // Retries are not allowed because it requires the flow to be sync
71
+ if (
72
+ (typeof options.retries === 'number' && options.retries > 0) ||
73
+ (options.retries && typeof options.retries.retries === 'number' && options.retries.retries > 0)
74
+ ) {
75
+ throw Object.assign(new Error('Cannot use retries with the sync api'), { code: 'ESYNC' });
76
+ }
77
+
78
+ return options;
79
+ }
80
+
81
+ module.exports = {
82
+ toPromise,
83
+ toSync,
84
+ toSyncOptions,
85
+ };
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('graceful-fs');
5
+ const retry = require('retry');
6
+ const onExit = require('signal-exit');
7
+ const mtimePrecision = require('./mtime-precision');
8
+
9
+ const locks = {};
10
+
11
+ function getLockFile(file, options) {
12
+ return options.lockfilePath || `${file}.lock`;
13
+ }
14
+
15
+ function resolveCanonicalPath(file, options, callback) {
16
+ if (!options.realpath) {
17
+ return callback(null, path.resolve(file));
18
+ }
19
+
20
+ // Use realpath to resolve symlinks
21
+ // It also resolves relative paths
22
+ options.fs.realpath(file, callback);
23
+ }
24
+
25
+ function acquireLock(file, options, callback) {
26
+ const lockfilePath = getLockFile(file, options);
27
+
28
+ // Use mkdir to create the lockfile (atomic operation)
29
+ options.fs.mkdir(lockfilePath, (err) => {
30
+ if (!err) {
31
+ // At this point, we acquired the lock!
32
+ // Probe the mtime precision
33
+ return mtimePrecision.probe(lockfilePath, options.fs, (err, mtime, mtimePrecision) => {
34
+ // If it failed, try to remove the lock..
35
+ /* istanbul ignore if */
36
+ if (err) {
37
+ options.fs.rmdir(lockfilePath, () => {});
38
+
39
+ return callback(err);
40
+ }
41
+
42
+ callback(null, mtime, mtimePrecision);
43
+ });
44
+ }
45
+
46
+ // If error is not EEXIST then some other error occurred while locking
47
+ if (err.code !== 'EEXIST') {
48
+ return callback(err);
49
+ }
50
+
51
+ // Otherwise, check if lock is stale by analyzing the file mtime
52
+ if (options.stale <= 0) {
53
+ return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
54
+ }
55
+
56
+ options.fs.stat(lockfilePath, (err, stat) => {
57
+ if (err) {
58
+ // Retry if the lockfile has been removed (meanwhile)
59
+ // Skip stale check to avoid recursiveness
60
+ if (err.code === 'ENOENT') {
61
+ return acquireLock(file, { ...options, stale: 0 }, callback);
62
+ }
63
+
64
+ return callback(err);
65
+ }
66
+
67
+ if (!isLockStale(stat, options)) {
68
+ return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
69
+ }
70
+
71
+ // If it's stale, remove it and try again!
72
+ // Skip stale check to avoid recursiveness
73
+ removeLock(file, options, (err) => {
74
+ if (err) {
75
+ return callback(err);
76
+ }
77
+
78
+ acquireLock(file, { ...options, stale: 0 }, callback);
79
+ });
80
+ });
81
+ });
82
+ }
83
+
84
+ function isLockStale(stat, options) {
85
+ return stat.mtime.getTime() < Date.now() - options.stale;
86
+ }
87
+
88
+ function removeLock(file, options, callback) {
89
+ // Remove lockfile, ignoring ENOENT errors
90
+ options.fs.rmdir(getLockFile(file, options), (err) => {
91
+ if (err && err.code !== 'ENOENT') {
92
+ return callback(err);
93
+ }
94
+
95
+ callback();
96
+ });
97
+ }
98
+
99
+ function updateLock(file, options) {
100
+ const lock = locks[file];
101
+
102
+ // Just for safety, should never happen
103
+ /* istanbul ignore if */
104
+ if (lock.updateTimeout) {
105
+ return;
106
+ }
107
+
108
+ lock.updateDelay = lock.updateDelay || options.update;
109
+ lock.updateTimeout = setTimeout(() => {
110
+ lock.updateTimeout = null;
111
+
112
+ // Stat the file to check if mtime is still ours
113
+ // If it is, we can still recover from a system sleep or a busy event loop
114
+ options.fs.stat(lock.lockfilePath, (err, stat) => {
115
+ const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
116
+
117
+ // If it failed to update the lockfile, keep trying unless
118
+ // the lockfile was deleted or we are over the threshold
119
+ if (err) {
120
+ if (err.code === 'ENOENT' || isOverThreshold) {
121
+ return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
122
+ }
123
+
124
+ lock.updateDelay = 1000;
125
+
126
+ return updateLock(file, options);
127
+ }
128
+
129
+ const isMtimeOurs = lock.mtime.getTime() === stat.mtime.getTime();
130
+
131
+ if (!isMtimeOurs) {
132
+ return setLockAsCompromised(
133
+ file,
134
+ lock,
135
+ Object.assign(
136
+ new Error('Unable to update lock within the stale threshold'),
137
+ { code: 'ECOMPROMISED' }
138
+ ));
139
+ }
140
+
141
+ const mtime = mtimePrecision.getMtime(lock.mtimePrecision);
142
+
143
+ options.fs.utimes(lock.lockfilePath, mtime, mtime, (err) => {
144
+ const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
145
+
146
+ // Ignore if the lock was released
147
+ if (lock.released) {
148
+ return;
149
+ }
150
+
151
+ // If it failed to update the lockfile, keep trying unless
152
+ // the lockfile was deleted or we are over the threshold
153
+ if (err) {
154
+ if (err.code === 'ENOENT' || isOverThreshold) {
155
+ return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
156
+ }
157
+
158
+ lock.updateDelay = 1000;
159
+
160
+ return updateLock(file, options);
161
+ }
162
+
163
+ // All ok, keep updating..
164
+ lock.mtime = mtime;
165
+ lock.lastUpdate = Date.now();
166
+ lock.updateDelay = null;
167
+ updateLock(file, options);
168
+ });
169
+ });
170
+ }, lock.updateDelay);
171
+
172
+ // Unref the timer so that the nodejs process can exit freely
173
+ // This is safe because all acquired locks will be automatically released
174
+ // on process exit
175
+
176
+ // We first check that `lock.updateTimeout.unref` exists because some users
177
+ // may be using this module outside of NodeJS (e.g., in an electron app),
178
+ // and in those cases `setTimeout` return an integer.
179
+ /* istanbul ignore else */
180
+ if (lock.updateTimeout.unref) {
181
+ lock.updateTimeout.unref();
182
+ }
183
+ }
184
+
185
+ function setLockAsCompromised(file, lock, err) {
186
+ // Signal the lock has been released
187
+ lock.released = true;
188
+
189
+ // Cancel lock mtime update
190
+ // Just for safety, at this point updateTimeout should be null
191
+ /* istanbul ignore if */
192
+ if (lock.updateTimeout) {
193
+ clearTimeout(lock.updateTimeout);
194
+ }
195
+
196
+ if (locks[file] === lock) {
197
+ delete locks[file];
198
+ }
199
+
200
+ lock.options.onCompromised(err);
201
+ }
202
+
203
+ // ----------------------------------------------------------
204
+
205
+ function lock(file, options, callback) {
206
+ /* istanbul ignore next */
207
+ options = {
208
+ stale: 10000,
209
+ update: null,
210
+ realpath: true,
211
+ retries: 0,
212
+ fs,
213
+ onCompromised: (err) => { throw err; },
214
+ ...options,
215
+ };
216
+
217
+ options.retries = options.retries || 0;
218
+ options.retries = typeof options.retries === 'number' ? { retries: options.retries } : options.retries;
219
+ options.stale = Math.max(options.stale || 0, 2000);
220
+ options.update = options.update == null ? options.stale / 2 : options.update || 0;
221
+ options.update = Math.max(Math.min(options.update, options.stale / 2), 1000);
222
+
223
+ // Resolve to a canonical file path
224
+ resolveCanonicalPath(file, options, (err, file) => {
225
+ if (err) {
226
+ return callback(err);
227
+ }
228
+
229
+ // Attempt to acquire the lock
230
+ const operation = retry.operation(options.retries);
231
+
232
+ operation.attempt(() => {
233
+ acquireLock(file, options, (err, mtime, mtimePrecision) => {
234
+ if (operation.retry(err)) {
235
+ return;
236
+ }
237
+
238
+ if (err) {
239
+ return callback(operation.mainError());
240
+ }
241
+
242
+ // We now own the lock
243
+ const lock = locks[file] = {
244
+ lockfilePath: getLockFile(file, options),
245
+ mtime,
246
+ mtimePrecision,
247
+ options,
248
+ lastUpdate: Date.now(),
249
+ };
250
+
251
+ // We must keep the lock fresh to avoid staleness
252
+ updateLock(file, options);
253
+
254
+ callback(null, (releasedCallback) => {
255
+ if (lock.released) {
256
+ return releasedCallback &&
257
+ releasedCallback(Object.assign(new Error('Lock is already released'), { code: 'ERELEASED' }));
258
+ }
259
+
260
+ // Not necessary to use realpath twice when unlocking
261
+ unlock(file, { ...options, realpath: false }, releasedCallback);
262
+ });
263
+ });
264
+ });
265
+ });
266
+ }
267
+
268
+ function unlock(file, options, callback) {
269
+ options = {
270
+ fs,
271
+ realpath: true,
272
+ ...options,
273
+ };
274
+
275
+ // Resolve to a canonical file path
276
+ resolveCanonicalPath(file, options, (err, file) => {
277
+ if (err) {
278
+ return callback(err);
279
+ }
280
+
281
+ // Skip if the lock is not acquired
282
+ const lock = locks[file];
283
+
284
+ if (!lock) {
285
+ return callback(Object.assign(new Error('Lock is not acquired/owned by you'), { code: 'ENOTACQUIRED' }));
286
+ }
287
+
288
+ lock.updateTimeout && clearTimeout(lock.updateTimeout); // Cancel lock mtime update
289
+ lock.released = true; // Signal the lock has been released
290
+ delete locks[file]; // Delete from locks
291
+
292
+ removeLock(file, options, callback);
293
+ });
294
+ }
295
+
296
+ function check(file, options, callback) {
297
+ options = {
298
+ stale: 10000,
299
+ realpath: true,
300
+ fs,
301
+ ...options,
302
+ };
303
+
304
+ options.stale = Math.max(options.stale || 0, 2000);
305
+
306
+ // Resolve to a canonical file path
307
+ resolveCanonicalPath(file, options, (err, file) => {
308
+ if (err) {
309
+ return callback(err);
310
+ }
311
+
312
+ // Check if lockfile exists
313
+ options.fs.stat(getLockFile(file, options), (err, stat) => {
314
+ if (err) {
315
+ // If does not exist, file is not locked. Otherwise, callback with error
316
+ return err.code === 'ENOENT' ? callback(null, false) : callback(err);
317
+ }
318
+
319
+ // Otherwise, check if lock is stale by analyzing the file mtime
320
+ return callback(null, !isLockStale(stat, options));
321
+ });
322
+ });
323
+ }
324
+
325
+ function getLocks() {
326
+ return locks;
327
+ }
328
+
329
+ // Remove acquired locks on exit
330
+ /* istanbul ignore next */
331
+ onExit(() => {
332
+ for (const file in locks) {
333
+ const options = locks[file].options;
334
+
335
+ try { options.fs.rmdirSync(getLockFile(file, options)); } catch (e) { /* Empty */ }
336
+ }
337
+ });
338
+
339
+ module.exports.lock = lock;
340
+ module.exports.unlock = unlock;
341
+ module.exports.check = check;
342
+ module.exports.getLocks = getLocks;
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ const cacheSymbol = Symbol();
4
+
5
+ function probe(file, fs, callback) {
6
+ const cachedPrecision = fs[cacheSymbol];
7
+
8
+ if (cachedPrecision) {
9
+ return fs.stat(file, (err, stat) => {
10
+ /* istanbul ignore if */
11
+ if (err) {
12
+ return callback(err);
13
+ }
14
+
15
+ callback(null, stat.mtime, cachedPrecision);
16
+ });
17
+ }
18
+
19
+ // Set mtime by ceiling Date.now() to seconds + 5ms so that it's "not on the second"
20
+ const mtime = new Date((Math.ceil(Date.now() / 1000) * 1000) + 5);
21
+
22
+ fs.utimes(file, mtime, mtime, (err) => {
23
+ /* istanbul ignore if */
24
+ if (err) {
25
+ return callback(err);
26
+ }
27
+
28
+ fs.stat(file, (err, stat) => {
29
+ /* istanbul ignore if */
30
+ if (err) {
31
+ return callback(err);
32
+ }
33
+
34
+ const precision = stat.mtime.getTime() % 1000 === 0 ? 's' : 'ms';
35
+
36
+ // Cache the precision in a non-enumerable way
37
+ Object.defineProperty(fs, cacheSymbol, { value: precision });
38
+
39
+ callback(null, stat.mtime, precision);
40
+ });
41
+ });
42
+ }
43
+
44
+ function getMtime(precision) {
45
+ let now = Date.now();
46
+
47
+ if (precision === 's') {
48
+ now = Math.ceil(now / 1000) * 1000;
49
+ }
50
+
51
+ return new Date(now);
52
+ }
53
+
54
+ module.exports.probe = probe;
55
+ module.exports.getMtime = getMtime;