-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfile_lock.ts
More file actions
82 lines (75 loc) · 2.48 KB
/
file_lock.ts
File metadata and controls
82 lines (75 loc) · 2.48 KB
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
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
export class FileLock {
private lockFilePath: string;
private retryInterval: number;
private maxRetries: number;
constructor(lockName: string, retryInterval: number = 100, maxRetries: number = 600) { // Default: 60s timeout
const tmpDir = os.tmpdir();
this.lockFilePath = path.join(tmpDir, `${lockName}.lock`);
this.retryInterval = retryInterval;
this.maxRetries = maxRetries;
}
async acquire(): Promise<boolean> {
for (let i = 0; i < this.maxRetries; i++) {
try {
// 'wx' flag fails if file exists
const fd = fs.openSync(this.lockFilePath, 'wx');
// Add PID to lock file for debugging and stale check
fs.writeSync(fd, process.pid.toString());
fs.closeSync(fd);
return true;
} catch (e: any) {
if (e.code === 'EEXIST') {
// Check for stale lock
try {
const pid = parseInt(fs.readFileSync(this.lockFilePath, 'utf-8'), 10);
if (!isNaN(pid)) {
try {
process.kill(pid, 0); // Check if process exists
} catch (err: any) {
if (err.code === 'ESRCH') {
// Process dead, remove stale lock
try { fs.unlinkSync(this.lockFilePath); } catch (ignore) {}
continue; // Retry immediately
}
}
}
} catch (err) {
// Ignore read errors, maybe it was just deleted
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, this.retryInterval));
} else {
throw e;
}
}
}
return false;
}
release(): void {
try {
if (fs.existsSync(this.lockFilePath)) {
// Only delete if we own it?
// Simplified: Just delete. The cooperative nature assumes good faith.
fs.unlinkSync(this.lockFilePath);
}
} catch (e) {
// Ignore errors on release
}
}
// Static helper for easy use
static async withLock<T>(lockName: string, action: () => Promise<T>): Promise<T> {
const lock = new FileLock(lockName);
if (await lock.acquire()) {
try {
return await action();
} finally {
lock.release();
}
} else {
throw new Error(`Failed to acquire lock: ${lockName}`);
}
}
}