bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
180 lines (157 loc) • 6.29 kB
text/typescript
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import * as os from 'os';
// 锁信息接口
interface LockInfo {
pid: number;
timestamp: number;
workspacePath: string;
instanceId: string;
}
// 执行选项接口
interface ExecutionOptions {
timeout?: number; // 锁超时时间(毫秒),默认30000ms
retryDelay?: number; // 重试延迟(毫秒),默认1000ms
maxRetries?: number; // 最大重试次数,默认3次
cleanupDelay?: number; // 执行完成后清理延迟(毫秒),默认2000ms
}
/**
* 文件锁管理器 - 防止多个VS Code窗口重复执行同一操作
*/
export class FileLockManager {
private lockDir: string;
private instanceId: string;
constructor(private workspacePath: string, private lockName: string = 'ui-progress') {
this.lockDir = os.tmpdir();
this.instanceId = `${process.pid}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 获取锁文件路径
*/
private getLockFilePath(): string {
const workspaceHash = crypto.createHash('md5').update(this.workspacePath).digest('hex');
return path.join(this.lockDir, `${this.lockName}-${workspaceHash}.lock`);
}
/**
* 检查进程是否存活
*/
private isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (error) {
return false;
}
}
/**
* 创建锁文件
*/
private createLock(lockFilePath: string): boolean {
try {
const lockInfo: LockInfo = {
pid: process.pid,
timestamp: Date.now(),
workspacePath: this.workspacePath,
instanceId: this.instanceId
};
fs.writeFileSync(lockFilePath, JSON.stringify(lockInfo, null, 2), { flag: 'wx' });
console.log(`✅ [SingleInstance] 锁文件创建成功: ${lockFilePath}`);
return true;
} catch (error: any) {
if (error.code === 'EEXIST') {
console.log(`🔒 [SingleInstance] 锁文件已存在: ${lockFilePath}`);
return false;
}
console.error(`❌ [SingleInstance] 创建锁文件失败:`, error);
return false;
}
}
/**
* 检查并清理过期锁
*/
private checkAndCleanupLock(lockFilePath: string, timeout: number): boolean {
try {
if (!fs.existsSync(lockFilePath)) {
return true; // 锁不存在,可以创建
}
const lockContent = fs.readFileSync(lockFilePath, 'utf8');
const lockInfo: LockInfo = JSON.parse(lockContent);
// 检查锁是否超时
const now = Date.now();
const isExpired = (now - lockInfo.timestamp) > timeout;
// 检查持锁进程是否还活着
const isProcessDead = !this.isProcessAlive(lockInfo.pid);
if (isExpired || isProcessDead) {
console.log(`🧹 [SingleInstance] 清理过期或死锁文件: ${lockFilePath}`);
fs.unlinkSync(lockFilePath);
return true; // 可以创建新锁
}
console.log(`⏳ [SingleInstance] 锁文件有效,其他实例正在处理 (PID: ${lockInfo.pid})`);
return false; // 锁仍然有效
} catch (error) {
console.error(`❌ [SingleInstance] 检查锁文件失败:`, error);
// 出错时删除锁文件,允许创建新锁
try {
fs.unlinkSync(lockFilePath);
} catch { }
return true;
}
}
/**
* 执行带锁保护的操作
*/
async executeWithLock<T>(
operation: () => Promise<T> | T,
options: ExecutionOptions = {}
): Promise<T | null> {
const {
timeout = 30000,
retryDelay = 1000,
maxRetries = 3,
cleanupDelay = 2000
} = options;
const lockFilePath = this.getLockFilePath();
let retryCount = 0;
while (retryCount < maxRetries) {
// 检查并清理过期锁
const canCreateLock = this.checkAndCleanupLock(lockFilePath, timeout);
if (canCreateLock && this.createLock(lockFilePath)) {
try {
console.log(`🚀 [SingleInstance] 开始执行操作 (Instance: ${this.instanceId})`);
const result = await operation();
console.log(`✅ [SingleInstance] 操作执行成功`);
// 延迟清理锁文件,避免其他实例立即重复执行
setTimeout(() => {
try {
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
console.log(`🧹 [SingleInstance] 锁文件已清理: ${lockFilePath}`);
}
} catch (error) {
console.error(`❌ [SingleInstance] 清理锁文件失败:`, error);
}
}, cleanupDelay);
return result;
} catch (error) {
console.error(`❌ [SingleInstance] 执行操作失败:`, error);
// 立即清理锁文件
try {
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
} catch { }
throw error;
}
} else {
console.log(`⏸️ [SingleInstance] 其他实例正在处理,等待重试... (${retryCount + 1}/${maxRetries})`);
retryCount++;
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
}
console.log(`🔄 [SingleInstance] 达到最大重试次数,跳过执行`);
return null; // 达到最大重试次数,跳过执行
}
}