UNPKG

redis-smq-common

Version:

Provides essential components and utilities shared across RedisSMQ packages.

176 lines 7.31 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileLock = void 0; const promises_1 = require("node:fs/promises"); const node_path_1 = require("node:path"); const index_js_1 = require("../env/index.js"); const index_js_2 = require("./errors/index.js"); function isAccessDeniedError(err) { return (err instanceof Error && 'code' in err && (err.code === 'EACCES' || err.code === 'EPERM')); } function isFileExistsError(err) { return err instanceof Error && 'code' in err && err.code === 'EEXIST'; } function isFileNotFoundError(err) { return err instanceof Error && 'code' in err && err.code === 'ENOENT'; } function getErrorMessage(err) { return err instanceof Error ? err.message : JSON.stringify(err); } class FileLock { constructor() { this.defaultRetryDelay = 1000; this.defaultRetries = 100; this.defaultStaleTimeout = 5 * 60 * 1000; this.defaultUpdateInterval = 60 * 1000; this.setupCleanup(); } setupCleanup() { const cleanUp = () => __awaiter(this, void 0, void 0, function* () { for (const [lockFile, lockInfo] of FileLock.activeLocks.entries()) { try { if (lockInfo.updateInterval) { clearInterval(lockInfo.updateInterval); } yield lockInfo.fileHandle.close(); yield (0, promises_1.unlink)(lockFile).catch(() => { }); } catch (err) { console.error(`Error releasing lock ${lockFile} during exit:`, err); } } FileLock.activeLocks.clear(); }); process.once('exit', cleanUp); process.once('SIGINT', cleanUp); process.once('SIGTERM', cleanUp); process.once('SIGUSR2', cleanUp); } updateLockFileMtime(lockFile) { return __awaiter(this, void 0, void 0, function* () { try { const now = new Date(); yield (0, promises_1.utimes)(lockFile, now, now); } catch (err) { if (isFileNotFoundError(err)) { const lockInfo = FileLock.activeLocks.get(lockFile); if (lockInfo === null || lockInfo === void 0 ? void 0 : lockInfo.updateInterval) { clearInterval(lockInfo.updateInterval); lockInfo.updateInterval = null; } } } }); } isLockStale(lockFile_1) { return __awaiter(this, arguments, void 0, function* (lockFile, staleTimeout = this.defaultStaleTimeout) { try { const stats = yield (0, promises_1.stat)(lockFile); const now = Date.now(); const mtime = stats.mtime.getTime(); return now - mtime > staleTimeout; } catch (err) { if (isFileNotFoundError(err)) { return false; } return true; } }); } acquireLock(lockFile_1) { return __awaiter(this, arguments, void 0, function* (lockFile, options = {}) { if (this.isLockHeld(lockFile)) { return; } const { retries = this.defaultRetries, retryDelay = this.defaultRetryDelay, staleTimeout = this.defaultStaleTimeout, updateInterval = this.defaultUpdateInterval, } = options; let attempts = 0; yield index_js_1.env.ensureDirectoryExists((0, node_path_1.dirname)(lockFile)); while (attempts < retries) { try { const fileHandle = yield (0, promises_1.open)(lockFile, 'wx'); const intervalId = setInterval(() => { this.updateLockFileMtime(lockFile).catch(() => { }); }, updateInterval); FileLock.activeLocks.set(lockFile, { fileHandle, updateInterval: intervalId, }); return; } catch (err) { if (isAccessDeniedError(err) || isFileExistsError(err)) { const isStale = yield this.isLockStale(lockFile, staleTimeout); if (isStale) { try { yield (0, promises_1.unlink)(lockFile); continue; } catch (unlinkErr) { if (!isFileNotFoundError(unlinkErr)) { console.warn(`Failed to remove stale lock file ${lockFile}: ${getErrorMessage(unlinkErr)}`); } } } yield new Promise((resolve) => setTimeout(resolve, retryDelay)); attempts++; continue; } throw new index_js_2.AcquireLockError({ metadata: { lockFile, error: getErrorMessage(lockFile), }, }); } } throw new index_js_2.AttemptsExhaustedError({ metadata: { lockFile, retries }, }); }); } releaseLock(lockFile) { return __awaiter(this, void 0, void 0, function* () { const lockInfo = FileLock.activeLocks.get(lockFile); if (!lockInfo) { return; } try { if (lockInfo.updateInterval) { clearInterval(lockInfo.updateInterval); } yield lockInfo.fileHandle.close(); FileLock.activeLocks.delete(lockFile); yield (0, promises_1.unlink)(lockFile); } catch (err) { if (!isFileNotFoundError(err)) { throw new index_js_2.ReleaseLockError({ metadata: { lockFile, error: getErrorMessage(err), }, }); } } }); } isLockHeld(lockFile) { return FileLock.activeLocks.has(lockFile); } } exports.FileLock = FileLock; FileLock.activeLocks = new Map(); //# sourceMappingURL=file-lock.js.map