atomically 1.3.2 → 1.7.0
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.
- package/dist/consts.d.ts +2 -1
- package/dist/consts.js +3 -1
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.js +2 -0
- package/dist/utils/retryify.js +14 -7
- package/dist/utils/retryify_queue.d.ts +15 -0
- package/dist/utils/retryify_queue.js +58 -0
- package/package.json +1 -1
- package/src/consts.ts +3 -1
- package/src/utils/fs.ts +2 -0
- package/src/utils/retryify.ts +24 -7
- package/src/utils/retryify_queue.ts +95 -0
- package/test/basic.js +2 -2
package/dist/consts.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ declare const DEFAULT_TIMEOUT_SYNC = 100;
|
|
|
8
8
|
declare const IS_POSIX = true;
|
|
9
9
|
declare const IS_USER_ROOT: boolean;
|
|
10
10
|
declare const LIMIT_BASENAME_LENGTH = 128;
|
|
11
|
+
declare const LIMIT_FILES_DESCRIPTORS = 10000;
|
|
11
12
|
declare const NOOP: () => void;
|
|
12
|
-
export { DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, NOOP };
|
|
13
|
+
export { DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, LIMIT_FILES_DESCRIPTORS, NOOP };
|
package/dist/consts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* CONSTS */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.NOOP = exports.LIMIT_BASENAME_LENGTH = exports.IS_USER_ROOT = exports.IS_POSIX = exports.DEFAULT_TIMEOUT_SYNC = exports.DEFAULT_TIMEOUT_ASYNC = exports.DEFAULT_WRITE_OPTIONS = exports.DEFAULT_READ_OPTIONS = exports.DEFAULT_FOLDER_MODE = exports.DEFAULT_FILE_MODE = exports.DEFAULT_ENCODING = void 0;
|
|
4
|
+
exports.NOOP = exports.LIMIT_FILES_DESCRIPTORS = exports.LIMIT_BASENAME_LENGTH = exports.IS_USER_ROOT = exports.IS_POSIX = exports.DEFAULT_TIMEOUT_SYNC = exports.DEFAULT_TIMEOUT_ASYNC = exports.DEFAULT_WRITE_OPTIONS = exports.DEFAULT_READ_OPTIONS = exports.DEFAULT_FOLDER_MODE = exports.DEFAULT_FILE_MODE = exports.DEFAULT_ENCODING = void 0;
|
|
5
5
|
const DEFAULT_ENCODING = 'utf8';
|
|
6
6
|
exports.DEFAULT_ENCODING = DEFAULT_ENCODING;
|
|
7
7
|
const DEFAULT_FILE_MODE = 0o666;
|
|
@@ -22,5 +22,7 @@ const IS_USER_ROOT = process.getuid ? !process.getuid() : false;
|
|
|
22
22
|
exports.IS_USER_ROOT = IS_USER_ROOT;
|
|
23
23
|
const LIMIT_BASENAME_LENGTH = 128; //TODO: fetch the real limit from the filesystem //TODO: fetch the whole-path length limit too
|
|
24
24
|
exports.LIMIT_BASENAME_LENGTH = LIMIT_BASENAME_LENGTH;
|
|
25
|
+
const LIMIT_FILES_DESCRIPTORS = 10000; //TODO: fetch the real limit from the filesystem
|
|
26
|
+
exports.LIMIT_FILES_DESCRIPTORS = LIMIT_FILES_DESCRIPTORS;
|
|
25
27
|
const NOOP = () => { };
|
|
26
28
|
exports.NOOP = NOOP;
|
package/dist/utils/fs.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare const FS: {
|
|
|
14
14
|
openRetry: import("../types").FN<[number], typeof fs.open.__promisify__>;
|
|
15
15
|
readFileRetry: import("../types").FN<[number], typeof fs.readFile.__promisify__>;
|
|
16
16
|
renameRetry: import("../types").FN<[number], typeof fs.rename.__promisify__>;
|
|
17
|
+
statRetry: import("../types").FN<[number], typeof fs.stat.__promisify__>;
|
|
17
18
|
writeRetry: import("../types").FN<[number], typeof fs.write.__promisify__>;
|
|
18
19
|
chmodSyncAttempt: typeof fs.chmodSync;
|
|
19
20
|
chownSyncAttempt: typeof fs.chownSync;
|
|
@@ -27,6 +28,7 @@ declare const FS: {
|
|
|
27
28
|
openSyncRetry: import("../types").FN<[number], typeof fs.openSync>;
|
|
28
29
|
readFileSyncRetry: import("../types").FN<[number], typeof fs.readFileSync>;
|
|
29
30
|
renameSyncRetry: import("../types").FN<[number], typeof fs.renameSync>;
|
|
31
|
+
statSyncRetry: import("../types").FN<[number], typeof fs.statSync>;
|
|
30
32
|
writeSyncRetry: import("../types").FN<[number], typeof fs.writeSync>;
|
|
31
33
|
};
|
|
32
34
|
export default FS;
|
package/dist/utils/fs.js
CHANGED
|
@@ -21,6 +21,7 @@ const FS = {
|
|
|
21
21
|
openRetry: retryify_1.retryifyAsync(util_1.promisify(fs.open), fs_handlers_1.default.isRetriableError),
|
|
22
22
|
readFileRetry: retryify_1.retryifyAsync(util_1.promisify(fs.readFile), fs_handlers_1.default.isRetriableError),
|
|
23
23
|
renameRetry: retryify_1.retryifyAsync(util_1.promisify(fs.rename), fs_handlers_1.default.isRetriableError),
|
|
24
|
+
statRetry: retryify_1.retryifyAsync(util_1.promisify(fs.stat), fs_handlers_1.default.isRetriableError),
|
|
24
25
|
writeRetry: retryify_1.retryifyAsync(util_1.promisify(fs.write), fs_handlers_1.default.isRetriableError),
|
|
25
26
|
chmodSyncAttempt: attemptify_1.attemptifySync(fs.chmodSync, fs_handlers_1.default.onChangeError),
|
|
26
27
|
chownSyncAttempt: attemptify_1.attemptifySync(fs.chownSync, fs_handlers_1.default.onChangeError),
|
|
@@ -34,6 +35,7 @@ const FS = {
|
|
|
34
35
|
openSyncRetry: retryify_1.retryifySync(fs.openSync, fs_handlers_1.default.isRetriableError),
|
|
35
36
|
readFileSyncRetry: retryify_1.retryifySync(fs.readFileSync, fs_handlers_1.default.isRetriableError),
|
|
36
37
|
renameSyncRetry: retryify_1.retryifySync(fs.renameSync, fs_handlers_1.default.isRetriableError),
|
|
38
|
+
statSyncRetry: retryify_1.retryifySync(fs.statSync, fs_handlers_1.default.isRetriableError),
|
|
37
39
|
writeSyncRetry: retryify_1.retryifySync(fs.writeSync, fs_handlers_1.default.isRetriableError)
|
|
38
40
|
};
|
|
39
41
|
/* EXPORT */
|
package/dist/utils/retryify.js
CHANGED
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.retryifySync = exports.retryifyAsync = void 0;
|
|
5
|
+
const retryify_queue_1 = require("./retryify_queue");
|
|
5
6
|
/* RETRYIFY */
|
|
6
|
-
//TODO: Maybe publish this as a standalone package
|
|
7
|
-
//TODO: Implement a more sophisticaed, less intensive, retry strategy
|
|
8
7
|
const retryifyAsync = (fn, isRetriableError) => {
|
|
9
8
|
return function (timestamp) {
|
|
10
9
|
return function attempt() {
|
|
11
|
-
return
|
|
12
|
-
|
|
10
|
+
return retryify_queue_1.default.schedule().then(cleanup => {
|
|
11
|
+
return fn.apply(undefined, arguments).then(result => {
|
|
12
|
+
cleanup();
|
|
13
|
+
return result;
|
|
14
|
+
}, error => {
|
|
15
|
+
cleanup();
|
|
16
|
+
if (Date.now() >= timestamp)
|
|
17
|
+
throw error;
|
|
18
|
+
if (isRetriableError(error)) {
|
|
19
|
+
const delay = Math.round(100 + (400 * Math.random())), delayPromise = new Promise(resolve => setTimeout(resolve, delay));
|
|
20
|
+
return delayPromise.then(() => attempt.apply(undefined, arguments));
|
|
21
|
+
}
|
|
13
22
|
throw error;
|
|
14
|
-
|
|
15
|
-
return attempt.apply(undefined, arguments);
|
|
16
|
-
throw error;
|
|
23
|
+
});
|
|
17
24
|
});
|
|
18
25
|
};
|
|
19
26
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
declare const RetryfyQueue: {
|
|
3
|
+
interval: number;
|
|
4
|
+
intervalId: NodeJS.Timeout | undefined;
|
|
5
|
+
limit: number;
|
|
6
|
+
queueActive: Set<Function>;
|
|
7
|
+
queueWaiting: Set<Function>;
|
|
8
|
+
init: () => void;
|
|
9
|
+
reset: () => void;
|
|
10
|
+
add: (fn: Function) => void;
|
|
11
|
+
remove: (fn: Function) => void;
|
|
12
|
+
schedule: () => Promise<Function>;
|
|
13
|
+
tick: () => void;
|
|
14
|
+
};
|
|
15
|
+
export default RetryfyQueue;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const consts_1 = require("../consts");
|
|
5
|
+
/* RETRYIFY QUEUE */
|
|
6
|
+
const RetryfyQueue = {
|
|
7
|
+
interval: 25,
|
|
8
|
+
intervalId: undefined,
|
|
9
|
+
limit: consts_1.LIMIT_FILES_DESCRIPTORS,
|
|
10
|
+
queueActive: new Set(),
|
|
11
|
+
queueWaiting: new Set(),
|
|
12
|
+
init: () => {
|
|
13
|
+
if (RetryfyQueue.intervalId)
|
|
14
|
+
return;
|
|
15
|
+
RetryfyQueue.intervalId = setInterval(RetryfyQueue.tick, RetryfyQueue.interval);
|
|
16
|
+
},
|
|
17
|
+
reset: () => {
|
|
18
|
+
if (!RetryfyQueue.intervalId)
|
|
19
|
+
return;
|
|
20
|
+
clearInterval(RetryfyQueue.intervalId);
|
|
21
|
+
delete RetryfyQueue.intervalId;
|
|
22
|
+
},
|
|
23
|
+
add: (fn) => {
|
|
24
|
+
RetryfyQueue.queueWaiting.add(fn);
|
|
25
|
+
if (RetryfyQueue.queueActive.size < (RetryfyQueue.limit / 2)) { // Active queue not under preassure, executing immediately
|
|
26
|
+
RetryfyQueue.tick();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
RetryfyQueue.init();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
remove: (fn) => {
|
|
33
|
+
RetryfyQueue.queueWaiting.delete(fn);
|
|
34
|
+
RetryfyQueue.queueActive.delete(fn);
|
|
35
|
+
},
|
|
36
|
+
schedule: () => {
|
|
37
|
+
return new Promise(resolve => {
|
|
38
|
+
const cleanup = () => RetryfyQueue.remove(resolver);
|
|
39
|
+
const resolver = () => resolve(cleanup);
|
|
40
|
+
RetryfyQueue.add(resolver);
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
tick: () => {
|
|
44
|
+
if (RetryfyQueue.queueActive.size >= RetryfyQueue.limit)
|
|
45
|
+
return;
|
|
46
|
+
if (!RetryfyQueue.queueWaiting.size)
|
|
47
|
+
return RetryfyQueue.reset();
|
|
48
|
+
for (const fn of RetryfyQueue.queueWaiting) {
|
|
49
|
+
if (RetryfyQueue.queueActive.size >= RetryfyQueue.limit)
|
|
50
|
+
break;
|
|
51
|
+
RetryfyQueue.queueWaiting.delete(fn);
|
|
52
|
+
RetryfyQueue.queueActive.add(fn);
|
|
53
|
+
fn();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/* EXPORT */
|
|
58
|
+
exports.default = RetryfyQueue;
|
package/package.json
CHANGED
package/src/consts.ts
CHANGED
|
@@ -21,8 +21,10 @@ const IS_USER_ROOT = process.getuid ? !process.getuid () : false;
|
|
|
21
21
|
|
|
22
22
|
const LIMIT_BASENAME_LENGTH = 128; //TODO: fetch the real limit from the filesystem //TODO: fetch the whole-path length limit too
|
|
23
23
|
|
|
24
|
+
const LIMIT_FILES_DESCRIPTORS = 10000; //TODO: fetch the real limit from the filesystem
|
|
25
|
+
|
|
24
26
|
const NOOP = () => {};
|
|
25
27
|
|
|
26
28
|
/* EXPORT */
|
|
27
29
|
|
|
28
|
-
export {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, NOOP};
|
|
30
|
+
export {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, LIMIT_FILES_DESCRIPTORS, NOOP};
|
package/src/utils/fs.ts
CHANGED
|
@@ -25,6 +25,7 @@ const FS = {
|
|
|
25
25
|
openRetry: retryifyAsync ( promisify ( fs.open ), Handlers.isRetriableError ),
|
|
26
26
|
readFileRetry: retryifyAsync ( promisify ( fs.readFile ), Handlers.isRetriableError ),
|
|
27
27
|
renameRetry: retryifyAsync ( promisify ( fs.rename ), Handlers.isRetriableError ),
|
|
28
|
+
statRetry: retryifyAsync ( promisify ( fs.stat ), Handlers.isRetriableError ),
|
|
28
29
|
writeRetry: retryifyAsync ( promisify ( fs.write ), Handlers.isRetriableError ),
|
|
29
30
|
|
|
30
31
|
chmodSyncAttempt: attemptifySync ( fs.chmodSync, Handlers.onChangeError ),
|
|
@@ -40,6 +41,7 @@ const FS = {
|
|
|
40
41
|
openSyncRetry: retryifySync ( fs.openSync, Handlers.isRetriableError ),
|
|
41
42
|
readFileSyncRetry: retryifySync ( fs.readFileSync, Handlers.isRetriableError ),
|
|
42
43
|
renameSyncRetry: retryifySync ( fs.renameSync, Handlers.isRetriableError ),
|
|
44
|
+
statSyncRetry: retryifySync ( fs.statSync, Handlers.isRetriableError ),
|
|
43
45
|
writeSyncRetry: retryifySync ( fs.writeSync, Handlers.isRetriableError )
|
|
44
46
|
|
|
45
47
|
};
|
package/src/utils/retryify.ts
CHANGED
|
@@ -2,25 +2,42 @@
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
|
|
4
4
|
import {Exception, FN} from '../types';
|
|
5
|
+
import RetryfyQueue from './retryify_queue';
|
|
5
6
|
|
|
6
7
|
/* RETRYIFY */
|
|
7
8
|
|
|
8
|
-
//TODO: Maybe publish this as a standalone package
|
|
9
|
-
//TODO: Implement a more sophisticaed, less intensive, retry strategy
|
|
10
|
-
|
|
11
9
|
const retryifyAsync = <T extends FN> ( fn: T, isRetriableError: FN<[Exception], boolean | void> ): FN<[number], T> => {
|
|
12
10
|
|
|
13
11
|
return function ( timestamp: number ) {
|
|
14
12
|
|
|
15
13
|
return function attempt () {
|
|
16
14
|
|
|
17
|
-
return
|
|
15
|
+
return RetryfyQueue.schedule ().then ( cleanup => {
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
return fn.apply ( undefined, arguments ).then ( result => {
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
cleanup ();
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
return result;
|
|
22
|
+
|
|
23
|
+
}, error => {
|
|
24
|
+
|
|
25
|
+
cleanup ();
|
|
26
|
+
|
|
27
|
+
if ( Date.now () >= timestamp ) throw error;
|
|
28
|
+
|
|
29
|
+
if ( isRetriableError ( error ) ) {
|
|
30
|
+
|
|
31
|
+
const delay = Math.round ( 100 + ( 400 * Math.random () ) ),
|
|
32
|
+
delayPromise = new Promise ( resolve => setTimeout ( resolve, delay ) );
|
|
33
|
+
|
|
34
|
+
return delayPromise.then ( () => attempt.apply ( undefined, arguments ) );
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw error;
|
|
39
|
+
|
|
40
|
+
});
|
|
24
41
|
|
|
25
42
|
});
|
|
26
43
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
|
|
4
|
+
import {LIMIT_FILES_DESCRIPTORS} from '../consts';
|
|
5
|
+
|
|
6
|
+
/* RETRYIFY QUEUE */
|
|
7
|
+
|
|
8
|
+
const RetryfyQueue = {
|
|
9
|
+
|
|
10
|
+
interval: 25,
|
|
11
|
+
intervalId: <NodeJS.Timeout | undefined> undefined,
|
|
12
|
+
limit: LIMIT_FILES_DESCRIPTORS,
|
|
13
|
+
queueActive: new Set<Function> (),
|
|
14
|
+
queueWaiting: new Set<Function> (),
|
|
15
|
+
|
|
16
|
+
init: (): void => {
|
|
17
|
+
|
|
18
|
+
if ( RetryfyQueue.intervalId ) return;
|
|
19
|
+
|
|
20
|
+
RetryfyQueue.intervalId = setInterval ( RetryfyQueue.tick, RetryfyQueue.interval );
|
|
21
|
+
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
reset: (): void => {
|
|
25
|
+
|
|
26
|
+
if ( !RetryfyQueue.intervalId ) return;
|
|
27
|
+
|
|
28
|
+
clearInterval ( RetryfyQueue.intervalId );
|
|
29
|
+
|
|
30
|
+
delete RetryfyQueue.intervalId;
|
|
31
|
+
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
add: ( fn: Function ): void => {
|
|
35
|
+
|
|
36
|
+
RetryfyQueue.queueWaiting.add ( fn );
|
|
37
|
+
|
|
38
|
+
if ( RetryfyQueue.queueActive.size < ( RetryfyQueue.limit / 2 ) ) { // Active queue not under preassure, executing immediately
|
|
39
|
+
|
|
40
|
+
RetryfyQueue.tick ();
|
|
41
|
+
|
|
42
|
+
} else {
|
|
43
|
+
|
|
44
|
+
RetryfyQueue.init ();
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
remove: ( fn: Function ): void => {
|
|
51
|
+
|
|
52
|
+
RetryfyQueue.queueWaiting.delete ( fn );
|
|
53
|
+
|
|
54
|
+
RetryfyQueue.queueActive.delete ( fn );
|
|
55
|
+
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
schedule: (): Promise<Function> => {
|
|
59
|
+
|
|
60
|
+
return new Promise ( resolve => {
|
|
61
|
+
|
|
62
|
+
const cleanup = () => RetryfyQueue.remove ( resolver );
|
|
63
|
+
|
|
64
|
+
const resolver = () => resolve ( cleanup );
|
|
65
|
+
|
|
66
|
+
RetryfyQueue.add ( resolver );
|
|
67
|
+
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
tick: (): void => {
|
|
73
|
+
|
|
74
|
+
if ( RetryfyQueue.queueActive.size >= RetryfyQueue.limit ) return;
|
|
75
|
+
|
|
76
|
+
if ( !RetryfyQueue.queueWaiting.size ) return RetryfyQueue.reset ();
|
|
77
|
+
|
|
78
|
+
for ( const fn of RetryfyQueue.queueWaiting ) {
|
|
79
|
+
|
|
80
|
+
if ( RetryfyQueue.queueActive.size >= RetryfyQueue.limit ) break;
|
|
81
|
+
|
|
82
|
+
RetryfyQueue.queueWaiting.delete ( fn );
|
|
83
|
+
RetryfyQueue.queueActive.add ( fn );
|
|
84
|
+
|
|
85
|
+
fn ();
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/* EXPORT */
|
|
94
|
+
|
|
95
|
+
export default RetryfyQueue;
|
package/test/basic.js
CHANGED
|
@@ -116,7 +116,7 @@ const fsMock = Object.assign ( {}, fs, {
|
|
|
116
116
|
|
|
117
117
|
const makeUnstableAsyncFn = function () {
|
|
118
118
|
return function () {
|
|
119
|
-
if ( Math.random () <= .
|
|
119
|
+
if ( Math.random () <= .75 ) {
|
|
120
120
|
const code = _.shuffle ([ 'EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM' ])[0];
|
|
121
121
|
throw createErr ( code );
|
|
122
122
|
}
|
|
@@ -126,7 +126,7 @@ const makeUnstableAsyncFn = function () {
|
|
|
126
126
|
|
|
127
127
|
const makeUnstableSyncFn = function ( fn ) {
|
|
128
128
|
return function () {
|
|
129
|
-
if ( Math.random () <= .
|
|
129
|
+
if ( Math.random () <= .75 ) {
|
|
130
130
|
const code = _.shuffle ([ 'EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM' ])[0];
|
|
131
131
|
throw createErr ( code );
|
|
132
132
|
}
|