redis-smq-common
Version:
Provides essential components and utilities shared across RedisSMQ packages.
176 lines • 7.31 kB
JavaScript
;
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