Skip to content

Commit 0396651

Browse files
committed
fix: fallback to file lock when Redis unavailable
- Replace no-op lock with proper file lock fallback - Maintains lock protection even without Redis - Import proper-lockfile for file lock support
1 parent 897b9a9 commit 0396651

File tree

1 file changed

+45
-7
lines changed

1 file changed

+45
-7
lines changed

src/redis-lock.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77

88
import Redis from 'ioredis';
9+
import path from 'node:path';
10+
import fs from 'node:fs';
11+
import proper-lockfile from 'proper-lockfile';
912

1013
// 生成唯一 token
1114
function generateToken(): string {
@@ -60,13 +63,9 @@ export class RedisLockManager {
6063
await this.redis.ping();
6164
redisAvailable = true;
6265
} catch {
63-
// Redis 不可用,優雅降級 - 回傳 no-op lock
64-
console.warn('[RedisLock] Redis unavailable, using no-op lock (allow concurrent)');
65-
return async () => {}; // No-op release
66-
}
67-
68-
if (!redisAvailable) {
69-
return async () => {};
66+
// Redis 不可用,使用 file lock fallback
67+
console.warn('[RedisLock] Redis unavailable, using file lock fallback');
68+
return this.createFileLock(key, ttl);
7069
}
7170

7271
let attempts = 0;
@@ -131,6 +130,45 @@ export class RedisLockManager {
131130
private sleep(ms: number): Promise<void> {
132131
return new Promise(resolve => setTimeout(resolve, ms));
133132
}
133+
134+
/**
135+
* 建立 file lock(Redis 不可用時的 fallback)
136+
*/
137+
private createFileLock(key: string, ttl?: number): () => Promise<void> {
138+
const lockPath = path.join('/tmp', `.memory-lock-${key}.lock`);
139+
const lockTTL = (ttl || this.defaultTTL) / 1000; // proper-lockfile 用秒
140+
141+
// 確保目錄存在
142+
const dir = path.dirname(lockPath);
143+
if (!fs.existsSync(dir)) {
144+
fs.mkdirSync(dir, { recursive: true });
145+
}
146+
147+
// 同步取得 file lock
148+
try {
149+
proper_lockfile.lockSync(lockPath, {
150+
retries: {
151+
retries: 10,
152+
minTimeout: 1000,
153+
maxTimeout: 30000,
154+
},
155+
stale: lockTTL,
156+
});
157+
console.log(`[RedisLock] Acquired file lock for ${key}`);
158+
} catch (err) {
159+
console.warn(`[RedisLock] Failed to acquire file lock: ${err}`);
160+
}
161+
162+
// 回傳 release function
163+
return async () => {
164+
try {
165+
await proper_lockfile.unlock(lockPath);
166+
console.log(`[RedisLock] Released file lock for ${key}`);
167+
} catch (err) {
168+
console.warn(`[RedisLock] Failed to release file lock: ${err}`);
169+
}
170+
};
171+
}
134172
}
135173

136174
/**

0 commit comments

Comments
 (0)