From e3822fa92eccb2065072c427825350792e558a19 Mon Sep 17 00:00:00 2001 From: pppwtk Date: Mon, 15 Jun 2026 13:45:31 +0700 Subject: [PATCH] refactor: optimize lock and temp file management via centralized cleanup --- .gitignore | 3 ++- src/cleanup.ts | 57 ++++++++++++++++++++++++++++++++++++++++++ src/errors.ts | 23 ++++++----------- src/index.ts | 1 + src/store/jsonStore.ts | 2 +- 5 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 src/cleanup.ts diff --git a/.gitignore b/.gitignore index e5bd534..35f7c55 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ Thumbs.db # Temp files *.tmp -.zero-mock-*.tmp \ No newline at end of file +.zero-mock-tmp-*.json +.zero-mock-bak-*.json \ No newline at end of file diff --git a/src/cleanup.ts b/src/cleanup.ts new file mode 100644 index 0000000..4a4d82b --- /dev/null +++ b/src/cleanup.ts @@ -0,0 +1,57 @@ +import fs from 'fs'; +import { printFatal } from './errors'; + +const activeLocks = new Set(); +const cleanupTasks = new Set<() => void>(); + +export function registerLock(lockFilePath: string): void { + activeLocks.add(lockFilePath); +} + +export function releaseLock(lockFilePath: string): void { + activeLocks.delete(lockFilePath); + try { + if (fs.existsSync(lockFilePath)) { + fs.unlinkSync(lockFilePath); + } + } catch (_) { + // Ignore cleanup errors + } +} + +export function registerCleanupTask(task: () => void): void { + cleanupTasks.add(task); +} + +function runCleanup() { + for (const lockFilePath of activeLocks) { + try { + if (fs.existsSync(lockFilePath)) { + fs.unlinkSync(lockFilePath); + } + } catch (_) { + // Ignore cleanup errors + } + } + activeLocks.clear(); + + for (const task of cleanupTasks) { + try { + task(); + } catch (_) { + // Ignore cleanup task errors + } + } + cleanupTasks.clear(); +} + +// Module load time registry +process.on('exit', runCleanup); +process.on('SIGINT', () => { runCleanup(); process.exit(0); }); +process.on('SIGTERM', () => { runCleanup(); process.exit(0); }); +process.on('SIGHUP', () => { runCleanup(); process.exit(0); }); +process.on('uncaughtException', (err) => { + console.error(err); + runCleanup(); + printFatal('SERVER_CRASH', err.message); +}); diff --git a/src/errors.ts b/src/errors.ts index d5c15f6..ec5d15d 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,9 @@ import pc from 'picocolors'; import fs from 'fs'; import path from 'path'; +import os from 'os'; +import crypto from 'crypto'; +import { registerLock } from './cleanup'; export type ErrorCode = | 'FILE_NOT_FOUND' @@ -86,7 +89,9 @@ export function validateConfig(file: string, port: number): void { } export function acquireLock(file: string): void { - const lockFilePath = `${file}.zero-mock.lock`; + const absoluteDbPath = path.resolve(file); + const hash = crypto.createHash('md5').update(absoluteDbPath).digest('hex'); + const lockFilePath = path.join(os.tmpdir(), `zero-mock-${hash}.lock`); if (fs.existsSync(lockFilePath)) { try { @@ -127,19 +132,5 @@ export function acquireLock(file: string): void { } } - const cleanup = () => { - try { - if (fs.existsSync(lockFilePath)) { - fs.unlinkSync(lockFilePath); - } - } catch (_) { /* ignore cleanup errors */ } - }; - - process.on('exit', cleanup); - process.on('SIGINT', () => { cleanup(); process.exit(130); }); - process.on('SIGTERM', () => { cleanup(); process.exit(143); }); - process.on('uncaughtException', (err) => { - cleanup(); - printFatal('SERVER_CRASH', err.message); - }); + registerLock(lockFilePath); } diff --git a/src/index.ts b/src/index.ts index 5386ca9..82aeddf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { bootstrap } from "./server/bootstrap"; import { runWizard } from "./cli-wizard"; import { validateConfig, acquireLock, printError, printFatal } from "./errors"; import { clearSavedConfig } from "./config-store"; +import { registerCleanupTask } from "./cleanup"; type CliOpts = { file?: string; diff --git a/src/store/jsonStore.ts b/src/store/jsonStore.ts index e41ff13..cf4320c 100644 --- a/src/store/jsonStore.ts +++ b/src/store/jsonStore.ts @@ -73,7 +73,7 @@ class JsonStoreImpl { const run = async (): Promise => { const target = this.backingPath!; const dir = dirname(target); - const tmp = join(dir, `.zero-mock-${randomBytes(8).toString("hex")}.tmp`); + const tmp = join(dir, `.zero-mock-tmp-${randomBytes(8).toString("hex")}.json`); const payload = `${JSON.stringify(this.data, null, 2)}\n`; try { await writeFile(tmp, payload, "utf8");