-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
lockfile.ts
111 lines (104 loc) · 2.72 KB
/
lockfile.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import fs from 'fs';
import { exists, stat, sleep } from './lockutils.js';
interface LockOption {
waitTimeMS?: number;
retryCount?: number;
deadlockTimeMS?: number;
name?: string;
}
/**
* lock filepath
* @param dirPath
* @param options
* @param callback
* @returns options.name
*/
export async function lock (dirPath: string, options: LockOption, callback: Function): Promise<string> {
// check options
if (typeof options.waitTimeMS !== 'number') {
options.waitTimeMS = 300;
}
if (typeof options.retryCount !== 'number') {
options.retryCount = 10;
}
if (typeof options.deadlockTimeMS !== 'number') {
options.deadlockTimeMS = 1000 * 30; // over 30sec
}
if (typeof options.name === 'undefined') {
options.name = '?';
}
// console.log('@options=', options);
// task
const executeTask = async (): Promise<string> => {
let errMsg = '';
try {
fs.mkdirSync(dirPath, { recursive: true, mode: 0o777 });
} catch (err: any) {
throw new Error('Could not get lock :' + err.message);
}
try {
await callback();
} catch (err: any) {
errMsg = `Task error: ${err.message}`;
}
try {
fs.rmdirSync(dirPath);
} catch (err) {
// unknown reason unlock
}
// throw callback error
if (errMsg !== '') { throw new Error(errMsg); }
return (options.name) ? options.name : '';
};
// check lock path
let lockStats = null;
try {
lockStats = fs.statSync(dirPath);
} catch (err) {
// no lock, can start task
return await executeTask();
}
// already exists lock
// check dead lock
const curTime = (new Date()).getTime();
const life = curTime - lockStats.ctimeMs;
if (life > options.deadlockTimeMS) {
try {
fs.rmdirSync(dirPath);
} catch (err: any) {
const canRemove = await rmdirRetry(dirPath, 10);
if (!canRemove) {
throw new Error('Could not remove dead lock : ' + err.message);
}
}
}
// wait for unlock
for (let i = 0; i < options.retryCount; i++) {
await sleep(options.waitTimeMS);
// check agin
try {
lockStats = fs.statSync(dirPath); // still locking
continue;
} catch (err: any) {
return await executeTask();
}
}
// could not make lock
throw new Error(`Could not get lock : path=${dirPath}`);
}
// rmdir
async function rmdirRetry(path: string, retryCount: number): Promise<boolean> {
if (retryCount <= 0) { return false; }
try {
// already not exits?
if (!fs.existsSync(path)) { return true; }
// try to rmdir
fs.rmdirSync(path, { recursive: true });
} catch (err) {
// could not remove dir
await sleep(100);
await rmdirRetry(path, retryCount-1);
}
return true;
}
export { sleep, exists, stat };