UNPKG

bytefun

Version:

一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台

180 lines (157 loc) 6.29 kB
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; // 达到最大重试次数,跳过执行 } }